media: make all media direct downloadable

The only exception is emojis.

Also changed direct download encoding field names to be more generic
when used in mixed manner depending on peer type.

Direct downloads are still somewhat inefficient as they require an API
round trip to succeed but we can cache things in the database if needed.
This commit is contained in:
Toni Spets
2025-05-05 17:01:31 +03:00
parent 9d3e9df57e
commit 7e75c8ef83
8 changed files with 341 additions and 150 deletions
+8 -9
View File
@@ -32,7 +32,6 @@ import (
"maunium.net/go/mautrix/event"
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
"go.mau.fi/mautrix-telegram/pkg/connector/media"
)
var (
@@ -186,7 +185,7 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID
return &chatInfo, isBroadcastChannel, nil
}
func (t *TelegramClient) avatarFromPhoto(ctx context.Context, photo tg.PhotoClass) *bridgev2.Avatar {
func (t *TelegramClient) avatarFromPhoto(ctx context.Context, peerType ids.PeerType, peerID int64, photo tg.PhotoClass) *bridgev2.Avatar {
if photo == nil {
zerolog.Ctx(ctx).Trace().Msg("Chat photo is nil, returning no avatar")
return nil
@@ -194,12 +193,12 @@ func (t *TelegramClient) avatarFromPhoto(ctx context.Context, photo tg.PhotoClas
zerolog.Ctx(ctx).Warn().Uint32("type_id", photo.TypeID()).Msg("Chat photo type unknown, returning no avatar")
return nil
}
return &bridgev2.Avatar{
ID: ids.MakeAvatarID(photo.GetID()),
Get: func(ctx context.Context) (data []byte, err error) {
return media.NewTransferer(t.client.API()).WithPhoto(photo).DownloadBytes(ctx)
},
avatar, err := t.convertPhoto(ctx, peerType, peerID, photo)
if err != nil {
zerolog.Ctx(ctx).Err(err).Int64("id", photo.GetID()).Msg("Failed to convert avatar")
return nil
}
return avatar
}
func (t *TelegramClient) filterChannelParticipants(participants []tg.ChannelParticipantClass, limit int) (members []bridgev2.ChatMember) {
@@ -260,7 +259,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
if !ok {
return nil, fmt.Errorf("full chat is %T not *tg.ChatFull", fullChat.FullChat)
}
chatInfo.Avatar = t.avatarFromPhoto(ctx, chatFull.ChatPhoto)
chatInfo.Avatar = t.avatarFromPhoto(ctx, peerType, id, chatFull.ChatPhoto)
if chatFull.Participants.TypeID() == tg.ChatParticipantsForbiddenTypeID {
chatInfo.Members.IsFull = false
@@ -324,7 +323,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
return nil, fmt.Errorf("too many participants (%d) in chat %d", channelFull.ParticipantsCount, id)
}
chatInfo.Avatar = t.avatarFromPhoto(ctx, channelFull.ChatPhoto)
chatInfo.Avatar = t.avatarFromPhoto(ctx, peerType, id, channelFull.ChatPhoto)
// TODO save available reactions?
// TODO save reactions limit?
+4 -10
View File
@@ -47,7 +47,6 @@ import (
"go.mau.fi/mautrix-telegram/pkg/connector/humanise"
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
"go.mau.fi/mautrix-telegram/pkg/connector/matrixfmt"
"go.mau.fi/mautrix-telegram/pkg/connector/media"
"go.mau.fi/mautrix-telegram/pkg/connector/store"
"go.mau.fi/mautrix-telegram/pkg/connector/telegramfmt"
"go.mau.fi/mautrix-telegram/pkg/connector/util"
@@ -668,15 +667,10 @@ func (t *TelegramClient) getUserInfoFromTelegramUser(ctx context.Context, u tg.U
var avatar *bridgev2.Avatar
if p, ok := user.GetPhoto(); ok && p.TypeID() == tg.UserProfilePhotoTypeID {
photo := p.(*tg.UserProfilePhoto)
avatar = &bridgev2.Avatar{
ID: ids.MakeAvatarID(photo.PhotoID),
Get: func(ctx context.Context) (data []byte, err error) {
transferer, err := media.NewTransferer(t.client.API()).WithUserPhoto(ctx, t.ScopedStore, user, photo.PhotoID)
if err != nil {
return nil, err
}
return transferer.DownloadBytes(ctx)
},
var err error
avatar, err = t.convertUserProfilePhoto(ctx, user.ID, photo)
if err != nil {
return nil, err
}
}
+112 -74
View File
@@ -44,21 +44,21 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
Any("info", info).
Logger()
ctx = log.WithContext(ctx)
log.Info().Msg("handling direct download")
log.Info().Any("info", info).Msg("handling direct download")
// TODO have an in-memory cache for media references?
userLogin, err := tc.Bridge.GetExistingUserLoginByID(ctx, ids.MakeUserLoginID(info.ReceiverID))
userLogin, err := tc.Bridge.GetExistingUserLoginByID(ctx, ids.MakeUserLoginID(info.UserID))
if err != nil {
if info.PeerType != ids.PeerTypeChannel {
return nil, fmt.Errorf("failed to get user login: %w", err)
}
logins, err := tc.Bridge.GetUserLoginsInPortal(ctx, ids.PeerTypeChannel.InternalAsPortalKey(info.ChatID, ""))
logins, err := tc.Bridge.GetUserLoginsInPortal(ctx, ids.PeerTypeChannel.InternalAsPortalKey(info.PeerID, ""))
if err != nil {
return nil, err
} else if len(logins) == 0 {
return nil, fmt.Errorf("no user logins in the portal (%s %d)", ids.PeerTypeChannel, info.ChatID)
return nil, fmt.Errorf("no user logins in the portal (%s %d)", ids.PeerTypeChannel, info.PeerID)
}
userLogin = logins[0]
}
@@ -75,33 +75,16 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
return nil, mautrix.MForbidden.WithMessage("User not logged in")
}
var messages tg.ModifiedMessagesMessages
switch info.PeerType {
case ids.PeerTypeUser, ids.PeerTypeChat:
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
m, err := client.client.API().MessagesGetMessages(ctx, []tg.InputMessageClass{
&tg.InputMessageID{ID: int(info.MessageID)},
})
if err != nil {
return nil, err
} else if messages, ok := m.(tg.ModifiedMessagesMessages); !ok {
return nil, fmt.Errorf("unsupported messages type %T", messages)
} else {
return messages, nil
}
})
case ids.PeerTypeChannel:
var accessHash int64
accessHash, err = client.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, info.ChatID)
if err != nil {
return nil, fmt.Errorf("failed to get channel access hash: %w", err)
} else {
transferer := media.NewTransferer(client.client.API())
var readyTransferer *media.ReadyTransferer
if info.MessageID > 0 {
var messages tg.ModifiedMessagesMessages
switch info.PeerType {
case ids.PeerTypeUser, ids.PeerTypeChat:
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
m, err := client.client.API().ChannelsGetMessages(ctx, &tg.ChannelsGetMessagesRequest{
Channel: &tg.InputChannel{ChannelID: info.ChatID, AccessHash: accessHash},
ID: []tg.InputMessageClass{
&tg.InputMessageID{ID: int(info.MessageID)},
},
m, err := client.client.API().MessagesGetMessages(ctx, []tg.InputMessageClass{
&tg.InputMessageID{ID: int(info.MessageID)},
})
if err != nil {
return nil, err
@@ -111,53 +94,108 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
return messages, nil
}
})
}
default:
return nil, fmt.Errorf("unknown peer type %s", info.PeerType)
}
if err != nil {
return nil, fmt.Errorf("failed to get messages for %+v: %w", info, err)
}
var msgMedia tg.MessageMediaClass
if len(messages.GetMessages()) != 1 {
return nil, fmt.Errorf("wrong number of messages retrieved %d", len(messages.GetMessages()))
} else if msg, ok := messages.GetMessages()[0].(*tg.Message); !ok {
return nil, fmt.Errorf("message was of the wrong type %s", messages.GetMessages()[0].TypeName())
} else if msg.ID != int(info.MessageID) {
return nil, fmt.Errorf("no media found with ID %d", info.MessageID)
} else {
msgMedia = msg.Media
}
transferer := media.NewTransferer(client.client.API())
var readyTransferer *media.ReadyTransferer
switch msgMedia := msgMedia.(type) {
case *tg.MessageMediaPhoto:
log.Debug().
Int64("photo_id", msgMedia.Photo.GetID()).
Msg("downloading photo")
readyTransferer = transferer.WithPhoto(msgMedia.Photo)
case *tg.MessageMediaDocument:
document, ok := msgMedia.Document.(*tg.Document)
if !ok {
return nil, fmt.Errorf("unknown document type %T", msgMedia.Document)
}
var isSticker bool
for _, attr := range document.GetAttributes() {
if attr.TypeID() == tg.DocumentAttributeStickerTypeID {
transferer = transferer.WithStickerConfig(tc.Config.AnimatedSticker)
isSticker = true
case ids.PeerTypeChannel:
var accessHash int64
accessHash, err = client.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, info.PeerID)
if err != nil {
return nil, fmt.Errorf("failed to get channel access hash: %w", err)
} else {
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
m, err := client.client.API().ChannelsGetMessages(ctx, &tg.ChannelsGetMessagesRequest{
Channel: &tg.InputChannel{ChannelID: info.PeerID, AccessHash: accessHash},
ID: []tg.InputMessageClass{
&tg.InputMessageID{ID: int(info.MessageID)},
},
})
if err != nil {
return nil, err
} else if messages, ok := m.(tg.ModifiedMessagesMessages); !ok {
return nil, fmt.Errorf("unsupported messages type %T", messages)
} else {
return messages, nil
}
})
}
default:
return nil, fmt.Errorf("unknown peer type %s", info.PeerType)
}
if err != nil {
return nil, fmt.Errorf("failed to get messages for %+v: %w", info, err)
}
log.Debug().
Int64("document_id", msgMedia.Document.GetID()).
Bool("is_sticker", isSticker).
Msg("downloading photo")
readyTransferer = transferer.WithDocument(msgMedia.Document, info.Thumbnail)
default:
return nil, fmt.Errorf("unhandled media type %T", msgMedia)
var msgMedia tg.MessageMediaClass
if len(messages.GetMessages()) != 1 {
return nil, fmt.Errorf("wrong number of messages retrieved %d", len(messages.GetMessages()))
} else if msg, ok := messages.GetMessages()[0].(*tg.Message); !ok {
return nil, fmt.Errorf("message was of the wrong type %s", messages.GetMessages()[0].TypeName())
} else if msg.ID != int(info.MessageID) {
return nil, fmt.Errorf("no media found with ID %d", info.MessageID)
} else {
msgMedia = msg.Media
}
switch msgMedia := msgMedia.(type) {
case *tg.MessageMediaPhoto:
log.Debug().
Int64("photo_id", msgMedia.Photo.GetID()).
Msg("downloading photo")
readyTransferer = transferer.WithPhoto(msgMedia.Photo)
case *tg.MessageMediaDocument:
document, ok := msgMedia.Document.(*tg.Document)
if !ok {
return nil, fmt.Errorf("unknown document type %T", msgMedia.Document)
}
var isSticker bool
for _, attr := range document.GetAttributes() {
if attr.TypeID() == tg.DocumentAttributeStickerTypeID {
transferer = transferer.WithStickerConfig(tc.Config.AnimatedSticker)
isSticker = true
}
}
log.Debug().
Int64("document_id", msgMedia.Document.GetID()).
Bool("is_sticker", isSticker).
Msg("downloading photo")
readyTransferer = transferer.WithDocument(msgMedia.Document, info.Thumbnail)
default:
return nil, fmt.Errorf("unhandled media type %T", msgMedia)
}
} else if info.PeerType == ids.PeerTypeUser {
readyTransferer, err = transferer.WithUserPhoto(ctx, client.ScopedStore, info.PeerID, info.ID)
if err != nil {
return nil, fmt.Errorf("failed to create user photo transferer: %w", err)
}
} else if info.PeerType == ids.PeerTypeChat {
fullChat, err := APICallWithUpdates(ctx, client, func() (*tg.MessagesChatFull, error) {
return client.client.API().MessagesGetFullChat(ctx, info.PeerID)
})
if err != nil {
return nil, err
}
chatFull, ok := fullChat.FullChat.(*tg.ChatFull)
if !ok {
return nil, fmt.Errorf("full chat is %T not *tg.ChatFull", fullChat.FullChat)
}
// FIXME: this is basically a not found error
if photoID := chatFull.ChatPhoto.GetID(); photoID != info.ID {
return nil, fmt.Errorf("photo id mismatch: %d != %d", photoID, info.ID)
}
readyTransferer = transferer.WithPhoto(chatFull.ChatPhoto)
} else if info.PeerType == ids.PeerTypeChannel {
accessHash, err := client.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, info.PeerID)
if err != nil {
return nil, fmt.Errorf("failed to get channel access hash: %w", err)
}
readyTransferer = transferer.WithChannelPhoto(info.PeerID, accessHash, info.ID)
}
if readyTransferer == nil {
return nil, fmt.Errorf("invalid combination of direct media keys")
}
r, mimeType, size, err := readyTransferer.Stream(ctx)
+77 -40
View File
@@ -17,6 +17,8 @@
package ids
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
@@ -33,59 +35,94 @@ import (
//
// 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)
// rrrrrrrr (int64) = the receiver ID (big endian)
// cccccccc (int64) = the Telegram peer ID (big endian)
// rrrrrrrr (int64) = the Telegram user ID (big endian)
// mmmmmmmm (int64) = the Telegram message ID (big endian)
// MMMMMMMM (int64) = the Telegram media ID (big endian)
// MMMMMMMM (int64) = the Telegram photo/file/document ID (big endian)
// T (byte) = 0 or 1 depending on whether it's a thumbnail
type DirectMediaInfo struct {
PeerType PeerType
ChatID int64
ReceiverID int64
MessageID int64
TelegramMediaID int64
Thumbnail bool
// Type of PeerID
PeerType PeerType
// Peer ID, may be channel, chat or user
PeerID int64
// Telegram user ID of the client that downloads this media
UserID int64
// Telegram message ID if related to a message
MessageID int64
// Telegram photo/file/document ID, depends on PeerType
ID int64
// Is this a thumbnail?
Thumbnail bool
}
func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) {
mediaID := []byte{
0x00, // Version
m.PeerType.AsByte(), // Peer Type
var mediaID networkid.MediaID
buf := &bytes.Buffer{}
// version byte
if err := binary.Write(buf, binary.BigEndian, byte(0)); err != nil {
return mediaID, err
}
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.ChatID)) // Telegram Chat ID
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.ReceiverID)) // Telegram Chat ID
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.MessageID)) // Telegram Message ID
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.TelegramMediaID)) // Telegram Media ID
if m.Thumbnail {
mediaID = append(mediaID, 0x01)
} else {
mediaID = append(mediaID, 0x00)
// v0
if err := binary.Write(buf, binary.BigEndian, m.PeerType.AsByte()); err != nil {
return mediaID, err
} else if err := binary.Write(buf, binary.BigEndian, m.PeerID); err != nil {
return mediaID, err
} else if err := binary.Write(buf, binary.BigEndian, m.UserID); err != nil {
return mediaID, err
} else if err := binary.Write(buf, binary.BigEndian, m.MessageID); err != nil {
return mediaID, err
} else if err := binary.Write(buf, binary.BigEndian, m.ID); err != nil {
return mediaID, err
} else if err := binary.Write(buf, binary.BigEndian, m.Thumbnail); err != nil {
return mediaID, err
}
return mediaID, nil
return networkid.MediaID(buf.Bytes()), 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) != 35 {
err = fmt.Errorf("invalid media ID")
return
return info, fmt.Errorf("empty media ID")
}
info.PeerType, err = PeerTypeFromByte(mediaID[1])
if err != nil {
return
buf := bytes.NewBuffer(mediaID)
// version byte
var version byte
if err := binary.Read(buf, binary.BigEndian, &version); err != nil {
return info, err
} else if version != 0 {
return info, fmt.Errorf("invalid version %d", version)
}
info.ChatID = int64(binary.BigEndian.Uint64(mediaID[2:]))
info.ReceiverID = int64(binary.BigEndian.Uint64(mediaID[10:]))
info.MessageID = int64(binary.BigEndian.Uint64(mediaID[18:]))
info.TelegramMediaID = int64(binary.BigEndian.Uint64(mediaID[26:]))
info.Thumbnail = mediaID[34] == 1
return
// v0
var peerType byte
if err := binary.Read(buf, binary.BigEndian, &peerType); err != nil {
return info, fmt.Errorf("failed to read peer type: %w", err)
} else if info.PeerType, err = PeerTypeFromByte(peerType); err != nil {
return info, fmt.Errorf("failed to convert peer type: %w", err)
} else if err := binary.Read(buf, binary.BigEndian, &info.PeerID); err != nil {
return info, fmt.Errorf("failed to read peer id: %w", err)
} else if err := binary.Read(buf, binary.BigEndian, &info.UserID); err != nil {
return info, fmt.Errorf("failed to read user id: %w", err)
} else if err := binary.Read(buf, binary.BigEndian, &info.MessageID); err != nil {
return info, fmt.Errorf("failed to message id: %w", err)
} else if err := binary.Read(buf, binary.BigEndian, &info.ID); err != nil {
return info, fmt.Errorf("failed to media id: %w", err)
} else if err := binary.Read(buf, binary.BigEndian, &info.Thumbnail); err != nil {
return info, fmt.Errorf("failed to thumbnail flag: %w", err)
}
return info, nil
}
func HashMediaID(mediaID networkid.MediaID) [32]byte {
return sha256.Sum256(mediaID)
}
+10 -10
View File
@@ -217,14 +217,14 @@ func (t *Transferer) WithPhoto(pc tg.PhotoClass) *ReadyTransferer {
// WithUserPhoto transforms a [Transferer] to a [ReadyTransferer] by setting
// the given user's photo as the location that will be downloaded by the
// [ReadyTransferer].
func (t *Transferer) WithUserPhoto(ctx context.Context, store *store.ScopedStore, user *tg.User, photoID int64) (*ReadyTransferer, error) {
if accessHash, err := store.GetAccessHash(ctx, ids.PeerTypeUser, user.GetID()); err != nil {
return nil, fmt.Errorf("failed to get user access hash for %d: %w", user.GetID(), err)
func (t *Transferer) WithUserPhoto(ctx context.Context, store *store.ScopedStore, userID int64, photoID int64) (*ReadyTransferer, error) {
if accessHash, err := store.GetAccessHash(ctx, ids.PeerTypeUser, userID); err != nil {
return nil, fmt.Errorf("failed to get user access hash for %d: %w", userID, err)
} else {
return &ReadyTransferer{
inner: t,
loc: &tg.InputPeerPhotoFileLocation{
Peer: &tg.InputPeerUser{UserID: user.GetID(), AccessHash: accessHash},
Peer: &tg.InputPeerUser{UserID: userID, AccessHash: accessHash},
PhotoID: photoID,
Big: true,
},
@@ -396,12 +396,12 @@ func (t *ReadyTransferer) DirectDownloadURL(ctx context.Context, loggedInUserID
return "", nil, err
}
mediaID, err := ids.DirectMediaInfo{
PeerType: peerType,
ChatID: chatID,
ReceiverID: loggedInUserID,
MessageID: int64(msgID),
Thumbnail: thumbnail,
TelegramMediaID: telegramMediaID,
PeerType: peerType,
PeerID: chatID,
UserID: loggedInUserID,
MessageID: int64(msgID),
Thumbnail: thumbnail,
ID: telegramMediaID,
}.AsMediaID()
if err != nil {
return "", nil, err
+26
View File
@@ -149,6 +149,22 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM
Msg("Not syncing portal because chat type is unsupported")
continue
}
fullChat, err := APICallWithUpdates(ctx, t, func() (*tg.MessagesChatFull, error) {
return t.client.API().MessagesGetFullChat(ctx, chat.GetID())
})
if err != nil {
log.Err(err).Msg("Failed to get full chat")
continue
}
chatFull, ok := fullChat.FullChat.(*tg.ChatFull)
var avatar *bridgev2.Avatar
if ok {
avatar, err = t.convertPhoto(ctx, ids.PeerTypeChat, chatFull.ID, chatFull.ChatPhoto)
if err != nil {
log.Err(err).Msg("Failed to convert group avatar")
}
}
chatInfo = &bridgev2.ChatInfo{
CanBackfill: true,
Name: &chat.(*tg.Chat).Title,
@@ -161,6 +177,7 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM
},
},
},
Avatar: avatar,
}
case *tg.PeerChannel:
channel := chats[peer.ChannelID]
@@ -176,6 +193,14 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM
Msg("Not syncing portal because channel type is unsupported")
continue
}
fullChannel := channel.(*tg.Channel)
var avatar *bridgev2.Avatar
if photo, ok := fullChannel.GetPhoto().(*tg.ChatPhoto); ok {
avatar, err = t.convertChatPhoto(ctx, fullChannel.ID, fullChannel.AccessHash, photo)
if err != nil {
log.Err(err).Msg("Failed to convert channel avatar")
}
}
chatInfo = &bridgev2.ChatInfo{
CanBackfill: true,
Name: &channel.(*tg.Channel).Title,
@@ -188,6 +213,7 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM
},
},
},
Avatar: avatar,
ExtraUpdates: func(ctx context.Context, p *bridgev2.Portal) bool {
return p.Metadata.(*PortalMetadata).SetIsSuperGroup(channel.(*tg.Channel).GetMegagroup())
},
+7 -7
View File
@@ -181,7 +181,6 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, entities tg.Ent
},
StreamOrder: int64(msg.GetID()),
}
switch action := msg.Action.(type) {
case *tg.MessageActionChatEditTitle:
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
@@ -189,9 +188,12 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, entities tg.Ent
ChatInfoChange: &bridgev2.ChatInfoChange{ChatInfo: &bridgev2.ChatInfo{Name: &action.Title}},
})
case *tg.MessageActionChatEditPhoto:
// FIXME
chatID := msg.PeerID.(*tg.PeerChat).ChatID
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
EventMeta: eventMeta.WithType(bridgev2.RemoteEventChatInfoChange),
ChatInfoChange: &bridgev2.ChatInfoChange{ChatInfo: &bridgev2.ChatInfo{Avatar: t.avatarFromPhoto(ctx, action.Photo)}},
ChatInfoChange: &bridgev2.ChatInfoChange{ChatInfo: &bridgev2.ChatInfo{Avatar: t.avatarFromPhoto(ctx, ids.PeerTypeChat, chatID, action.Photo)}},
})
case *tg.MessageActionChatDeletePhoto:
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
@@ -689,11 +691,9 @@ func (t *TelegramClient) updateChannel(ctx context.Context, channel *tg.Channel)
var avatar *bridgev2.Avatar
if photo, ok := channel.GetPhoto().(*tg.ChatPhoto); ok {
avatar = &bridgev2.Avatar{
ID: ids.MakeAvatarID(photo.PhotoID),
Get: func(ctx context.Context) (data []byte, err error) {
return media.NewTransferer(t.client.API()).WithChannelPhoto(channel.ID, channel.AccessHash, photo.PhotoID).DownloadBytes(ctx)
},
avatar, err = t.convertChatPhoto(ctx, channel.ID, channel.AccessHash, photo)
if err != nil {
return nil, err
}
}
+97
View File
@@ -765,3 +765,100 @@ func convertGame(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart {
},
}
}
func (c *TelegramClient) convertUserProfilePhoto(ctx context.Context, userID int64, photo *tg.UserProfilePhoto) (*bridgev2.Avatar, error) {
avatar := &bridgev2.Avatar{
ID: ids.MakeAvatarID(photo.PhotoID),
}
if c.main.useDirectMedia {
mediaID, err := ids.DirectMediaInfo{
PeerType: ids.PeerTypeUser,
PeerID: userID,
UserID: c.telegramUserID,
ID: photo.PhotoID,
}.AsMediaID()
if err != nil {
return nil, err
}
if avatar.MXC, err = c.main.Bridge.Matrix.GenerateContentURI(ctx, mediaID); err != nil {
return nil, err
}
avatar.Hash = ids.HashMediaID(mediaID)
} else {
avatar.Get = func(ctx context.Context) (data []byte, err error) {
transferer, err := media.NewTransferer(c.client.API()).WithUserPhoto(ctx, c.ScopedStore, userID, photo.PhotoID)
if err != nil {
return nil, err
}
return transferer.DownloadBytes(ctx)
}
}
return avatar, nil
}
func (c *TelegramClient) convertChatPhoto(ctx context.Context, channelID, accessHash int64, chatPhoto *tg.ChatPhoto) (*bridgev2.Avatar, error) {
avatar := &bridgev2.Avatar{
ID: ids.MakeAvatarID(chatPhoto.PhotoID),
}
if c.main.useDirectMedia {
mediaID, err := ids.DirectMediaInfo{
PeerType: ids.PeerTypeChannel,
PeerID: channelID,
UserID: c.telegramUserID,
ID: chatPhoto.PhotoID,
}.AsMediaID()
if err != nil {
return nil, err
}
if avatar.MXC, err = c.main.Bridge.Matrix.GenerateContentURI(ctx, mediaID); err != nil {
return nil, err
}
avatar.Hash = ids.HashMediaID(mediaID)
} else {
avatar.Get = func(ctx context.Context) (data []byte, err error) {
return media.NewTransferer(c.client.API()).WithChannelPhoto(channelID, accessHash, chatPhoto.PhotoID).DownloadBytes(ctx)
}
}
return avatar, nil
}
func (c *TelegramClient) convertPhoto(ctx context.Context, peerType ids.PeerType, peerID int64, photoClass tg.PhotoClass) (*bridgev2.Avatar, error) {
photo, ok := photoClass.(*tg.Photo)
if !ok {
return nil, fmt.Errorf("not a photo: %T", photoClass)
}
avatar := &bridgev2.Avatar{
ID: ids.MakeAvatarID(photo.GetID()),
}
if c.main.useDirectMedia {
mediaID, err := ids.DirectMediaInfo{
PeerType: peerType,
PeerID: peerID,
UserID: c.telegramUserID,
ID: photo.GetID(),
}.AsMediaID()
if err != nil {
return nil, err
}
if avatar.MXC, err = c.main.Bridge.Matrix.GenerateContentURI(ctx, mediaID); err != nil {
return nil, err
}
avatar.Hash = ids.HashMediaID(mediaID)
} else {
avatar.Get = func(ctx context.Context) (data []byte, err error) {
return media.NewTransferer(c.client.API()).WithPhoto(photo).DownloadBytes(ctx)
}
}
return avatar, nil
}