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:
@@ -32,7 +32,6 @@ import (
|
|||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||||
"go.mau.fi/mautrix-telegram/pkg/connector/media"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -186,7 +185,7 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID
|
|||||||
return &chatInfo, isBroadcastChannel, nil
|
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 {
|
if photo == nil {
|
||||||
zerolog.Ctx(ctx).Trace().Msg("Chat photo is nil, returning no avatar")
|
zerolog.Ctx(ctx).Trace().Msg("Chat photo is nil, returning no avatar")
|
||||||
return nil
|
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")
|
zerolog.Ctx(ctx).Warn().Uint32("type_id", photo.TypeID()).Msg("Chat photo type unknown, returning no avatar")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &bridgev2.Avatar{
|
avatar, err := t.convertPhoto(ctx, peerType, peerID, photo)
|
||||||
ID: ids.MakeAvatarID(photo.GetID()),
|
if err != nil {
|
||||||
Get: func(ctx context.Context) (data []byte, err error) {
|
zerolog.Ctx(ctx).Err(err).Int64("id", photo.GetID()).Msg("Failed to convert avatar")
|
||||||
return media.NewTransferer(t.client.API()).WithPhoto(photo).DownloadBytes(ctx)
|
return nil
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
return avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TelegramClient) filterChannelParticipants(participants []tg.ChannelParticipantClass, limit int) (members []bridgev2.ChatMember) {
|
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 {
|
if !ok {
|
||||||
return nil, fmt.Errorf("full chat is %T not *tg.ChatFull", fullChat.FullChat)
|
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 {
|
if chatFull.Participants.TypeID() == tg.ChatParticipantsForbiddenTypeID {
|
||||||
chatInfo.Members.IsFull = false
|
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)
|
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 available reactions?
|
||||||
// TODO save reactions limit?
|
// TODO save reactions limit?
|
||||||
|
|||||||
+4
-10
@@ -47,7 +47,6 @@ import (
|
|||||||
"go.mau.fi/mautrix-telegram/pkg/connector/humanise"
|
"go.mau.fi/mautrix-telegram/pkg/connector/humanise"
|
||||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||||
"go.mau.fi/mautrix-telegram/pkg/connector/matrixfmt"
|
"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/store"
|
||||||
"go.mau.fi/mautrix-telegram/pkg/connector/telegramfmt"
|
"go.mau.fi/mautrix-telegram/pkg/connector/telegramfmt"
|
||||||
"go.mau.fi/mautrix-telegram/pkg/connector/util"
|
"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
|
var avatar *bridgev2.Avatar
|
||||||
if p, ok := user.GetPhoto(); ok && p.TypeID() == tg.UserProfilePhotoTypeID {
|
if p, ok := user.GetPhoto(); ok && p.TypeID() == tg.UserProfilePhotoTypeID {
|
||||||
photo := p.(*tg.UserProfilePhoto)
|
photo := p.(*tg.UserProfilePhoto)
|
||||||
avatar = &bridgev2.Avatar{
|
var err error
|
||||||
ID: ids.MakeAvatarID(photo.PhotoID),
|
avatar, err = t.convertUserProfilePhoto(ctx, user.ID, photo)
|
||||||
Get: func(ctx context.Context) (data []byte, err error) {
|
if err != nil {
|
||||||
transferer, err := media.NewTransferer(t.client.API()).WithUserPhoto(ctx, t.ScopedStore, user, photo.PhotoID)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return transferer.DownloadBytes(ctx)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+112
-74
@@ -44,21 +44,21 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
|
|||||||
Any("info", info).
|
Any("info", info).
|
||||||
Logger()
|
Logger()
|
||||||
ctx = log.WithContext(ctx)
|
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?
|
// 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 err != nil {
|
||||||
if info.PeerType != ids.PeerTypeChannel {
|
if info.PeerType != ids.PeerTypeChannel {
|
||||||
return nil, fmt.Errorf("failed to get user login: %w", err)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(logins) == 0 {
|
} 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]
|
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")
|
return nil, mautrix.MForbidden.WithMessage("User not logged in")
|
||||||
}
|
}
|
||||||
|
|
||||||
var messages tg.ModifiedMessagesMessages
|
transferer := media.NewTransferer(client.client.API())
|
||||||
switch info.PeerType {
|
var readyTransferer *media.ReadyTransferer
|
||||||
case ids.PeerTypeUser, ids.PeerTypeChat:
|
|
||||||
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
|
if info.MessageID > 0 {
|
||||||
m, err := client.client.API().MessagesGetMessages(ctx, []tg.InputMessageClass{
|
var messages tg.ModifiedMessagesMessages
|
||||||
&tg.InputMessageID{ID: int(info.MessageID)},
|
switch info.PeerType {
|
||||||
})
|
case ids.PeerTypeUser, ids.PeerTypeChat:
|
||||||
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 {
|
|
||||||
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
|
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
|
||||||
m, err := client.client.API().ChannelsGetMessages(ctx, &tg.ChannelsGetMessagesRequest{
|
m, err := client.client.API().MessagesGetMessages(ctx, []tg.InputMessageClass{
|
||||||
Channel: &tg.InputChannel{ChannelID: info.ChatID, AccessHash: accessHash},
|
&tg.InputMessageID{ID: int(info.MessageID)},
|
||||||
ID: []tg.InputMessageClass{
|
|
||||||
&tg.InputMessageID{ID: int(info.MessageID)},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -111,53 +94,108 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
|
|||||||
return messages, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
case ids.PeerTypeChannel:
|
||||||
default:
|
var accessHash int64
|
||||||
return nil, fmt.Errorf("unknown peer type %s", info.PeerType)
|
accessHash, err = client.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, info.PeerID)
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to get channel access hash: %w", err)
|
||||||
return nil, fmt.Errorf("failed to get messages for %+v: %w", info, err)
|
} else {
|
||||||
}
|
messages, err = APICallWithUpdates(ctx, client, func() (tg.ModifiedMessagesMessages, error) {
|
||||||
|
m, err := client.client.API().ChannelsGetMessages(ctx, &tg.ChannelsGetMessagesRequest{
|
||||||
var msgMedia tg.MessageMediaClass
|
Channel: &tg.InputChannel{ChannelID: info.PeerID, AccessHash: accessHash},
|
||||||
if len(messages.GetMessages()) != 1 {
|
ID: []tg.InputMessageClass{
|
||||||
return nil, fmt.Errorf("wrong number of messages retrieved %d", len(messages.GetMessages()))
|
&tg.InputMessageID{ID: int(info.MessageID)},
|
||||||
} 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) {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("no media found with ID %d", info.MessageID)
|
return nil, err
|
||||||
} else {
|
} else if messages, ok := m.(tg.ModifiedMessagesMessages); !ok {
|
||||||
msgMedia = msg.Media
|
return nil, fmt.Errorf("unsupported messages type %T", messages)
|
||||||
}
|
} else {
|
||||||
|
return messages, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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().
|
var msgMedia tg.MessageMediaClass
|
||||||
Int64("document_id", msgMedia.Document.GetID()).
|
if len(messages.GetMessages()) != 1 {
|
||||||
Bool("is_sticker", isSticker).
|
return nil, fmt.Errorf("wrong number of messages retrieved %d", len(messages.GetMessages()))
|
||||||
Msg("downloading photo")
|
} else if msg, ok := messages.GetMessages()[0].(*tg.Message); !ok {
|
||||||
readyTransferer = transferer.WithDocument(msgMedia.Document, info.Thumbnail)
|
return nil, fmt.Errorf("message was of the wrong type %s", messages.GetMessages()[0].TypeName())
|
||||||
default:
|
} else if msg.ID != int(info.MessageID) {
|
||||||
return nil, fmt.Errorf("unhandled media type %T", msgMedia)
|
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)
|
r, mimeType, size, err := readyTransferer.Stream(ctx)
|
||||||
|
|||||||
+77
-40
@@ -17,6 +17,8 @@
|
|||||||
package ids
|
package ids
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -33,59 +35,94 @@ import (
|
|||||||
//
|
//
|
||||||
// v (int8) = binary encoding format version. Should be 0.
|
// v (int8) = binary encoding format version. Should be 0.
|
||||||
// p (byte) = the peer type of the Telegram chat ID
|
// p (byte) = the peer type of the Telegram chat ID
|
||||||
// cccccccc (int64) = the Telegram chat ID (big endian)
|
// cccccccc (int64) = the Telegram peer ID (big endian)
|
||||||
// rrrrrrrr (int64) = the receiver ID (big endian)
|
// rrrrrrrr (int64) = the Telegram user ID (big endian)
|
||||||
// mmmmmmmm (int64) = the Telegram message 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
|
// T (byte) = 0 or 1 depending on whether it's a thumbnail
|
||||||
type DirectMediaInfo struct {
|
type DirectMediaInfo struct {
|
||||||
PeerType PeerType
|
// Type of PeerID
|
||||||
ChatID int64
|
PeerType PeerType
|
||||||
ReceiverID int64
|
|
||||||
MessageID int64
|
// Peer ID, may be channel, chat or user
|
||||||
TelegramMediaID int64
|
PeerID int64
|
||||||
Thumbnail bool
|
|
||||||
|
// 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) {
|
func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) {
|
||||||
mediaID := []byte{
|
var mediaID networkid.MediaID
|
||||||
0x00, // Version
|
buf := &bytes.Buffer{}
|
||||||
m.PeerType.AsByte(), // Peer Type
|
|
||||||
|
// 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
|
// v0
|
||||||
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.MessageID)) // Telegram Message ID
|
if err := binary.Write(buf, binary.BigEndian, m.PeerType.AsByte()); err != nil {
|
||||||
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.TelegramMediaID)) // Telegram Media ID
|
return mediaID, err
|
||||||
if m.Thumbnail {
|
} else if err := binary.Write(buf, binary.BigEndian, m.PeerID); err != nil {
|
||||||
mediaID = append(mediaID, 0x01)
|
return mediaID, err
|
||||||
} else {
|
} else if err := binary.Write(buf, binary.BigEndian, m.UserID); err != nil {
|
||||||
mediaID = append(mediaID, 0x00)
|
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) {
|
func ParseDirectMediaInfo(mediaID networkid.MediaID) (info DirectMediaInfo, err error) {
|
||||||
if len(mediaID) == 0 {
|
if len(mediaID) == 0 {
|
||||||
err = fmt.Errorf("empty media ID")
|
return info, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info.PeerType, err = PeerTypeFromByte(mediaID[1])
|
buf := bytes.NewBuffer(mediaID)
|
||||||
if err != nil {
|
|
||||||
return
|
// 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:]))
|
// v0
|
||||||
info.MessageID = int64(binary.BigEndian.Uint64(mediaID[18:]))
|
var peerType byte
|
||||||
info.TelegramMediaID = int64(binary.BigEndian.Uint64(mediaID[26:]))
|
if err := binary.Read(buf, binary.BigEndian, &peerType); err != nil {
|
||||||
info.Thumbnail = mediaID[34] == 1
|
return info, fmt.Errorf("failed to read peer type: %w", err)
|
||||||
return
|
} 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,14 +217,14 @@ func (t *Transferer) WithPhoto(pc tg.PhotoClass) *ReadyTransferer {
|
|||||||
// WithUserPhoto transforms a [Transferer] to a [ReadyTransferer] by setting
|
// WithUserPhoto transforms a [Transferer] to a [ReadyTransferer] by setting
|
||||||
// the given user's photo as the location that will be downloaded by the
|
// the given user's photo as the location that will be downloaded by the
|
||||||
// [ReadyTransferer].
|
// [ReadyTransferer].
|
||||||
func (t *Transferer) WithUserPhoto(ctx context.Context, store *store.ScopedStore, user *tg.User, photoID int64) (*ReadyTransferer, error) {
|
func (t *Transferer) WithUserPhoto(ctx context.Context, store *store.ScopedStore, userID int64, photoID int64) (*ReadyTransferer, error) {
|
||||||
if accessHash, err := store.GetAccessHash(ctx, ids.PeerTypeUser, user.GetID()); err != nil {
|
if accessHash, err := store.GetAccessHash(ctx, ids.PeerTypeUser, userID); err != nil {
|
||||||
return nil, fmt.Errorf("failed to get user access hash for %d: %w", user.GetID(), err)
|
return nil, fmt.Errorf("failed to get user access hash for %d: %w", userID, err)
|
||||||
} else {
|
} else {
|
||||||
return &ReadyTransferer{
|
return &ReadyTransferer{
|
||||||
inner: t,
|
inner: t,
|
||||||
loc: &tg.InputPeerPhotoFileLocation{
|
loc: &tg.InputPeerPhotoFileLocation{
|
||||||
Peer: &tg.InputPeerUser{UserID: user.GetID(), AccessHash: accessHash},
|
Peer: &tg.InputPeerUser{UserID: userID, AccessHash: accessHash},
|
||||||
PhotoID: photoID,
|
PhotoID: photoID,
|
||||||
Big: true,
|
Big: true,
|
||||||
},
|
},
|
||||||
@@ -396,12 +396,12 @@ func (t *ReadyTransferer) DirectDownloadURL(ctx context.Context, loggedInUserID
|
|||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
mediaID, err := ids.DirectMediaInfo{
|
mediaID, err := ids.DirectMediaInfo{
|
||||||
PeerType: peerType,
|
PeerType: peerType,
|
||||||
ChatID: chatID,
|
PeerID: chatID,
|
||||||
ReceiverID: loggedInUserID,
|
UserID: loggedInUserID,
|
||||||
MessageID: int64(msgID),
|
MessageID: int64(msgID),
|
||||||
Thumbnail: thumbnail,
|
Thumbnail: thumbnail,
|
||||||
TelegramMediaID: telegramMediaID,
|
ID: telegramMediaID,
|
||||||
}.AsMediaID()
|
}.AsMediaID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
|
|||||||
@@ -149,6 +149,22 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM
|
|||||||
Msg("Not syncing portal because chat type is unsupported")
|
Msg("Not syncing portal because chat type is unsupported")
|
||||||
continue
|
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{
|
chatInfo = &bridgev2.ChatInfo{
|
||||||
CanBackfill: true,
|
CanBackfill: true,
|
||||||
Name: &chat.(*tg.Chat).Title,
|
Name: &chat.(*tg.Chat).Title,
|
||||||
@@ -161,6 +177,7 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Avatar: avatar,
|
||||||
}
|
}
|
||||||
case *tg.PeerChannel:
|
case *tg.PeerChannel:
|
||||||
channel := chats[peer.ChannelID]
|
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")
|
Msg("Not syncing portal because channel type is unsupported")
|
||||||
continue
|
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{
|
chatInfo = &bridgev2.ChatInfo{
|
||||||
CanBackfill: true,
|
CanBackfill: true,
|
||||||
Name: &channel.(*tg.Channel).Title,
|
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 {
|
ExtraUpdates: func(ctx context.Context, p *bridgev2.Portal) bool {
|
||||||
return p.Metadata.(*PortalMetadata).SetIsSuperGroup(channel.(*tg.Channel).GetMegagroup())
|
return p.Metadata.(*PortalMetadata).SetIsSuperGroup(channel.(*tg.Channel).GetMegagroup())
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, entities tg.Ent
|
|||||||
},
|
},
|
||||||
StreamOrder: int64(msg.GetID()),
|
StreamOrder: int64(msg.GetID()),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch action := msg.Action.(type) {
|
switch action := msg.Action.(type) {
|
||||||
case *tg.MessageActionChatEditTitle:
|
case *tg.MessageActionChatEditTitle:
|
||||||
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
|
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}},
|
ChatInfoChange: &bridgev2.ChatInfoChange{ChatInfo: &bridgev2.ChatInfo{Name: &action.Title}},
|
||||||
})
|
})
|
||||||
case *tg.MessageActionChatEditPhoto:
|
case *tg.MessageActionChatEditPhoto:
|
||||||
|
// FIXME
|
||||||
|
chatID := msg.PeerID.(*tg.PeerChat).ChatID
|
||||||
|
|
||||||
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
|
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
|
||||||
EventMeta: eventMeta.WithType(bridgev2.RemoteEventChatInfoChange),
|
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:
|
case *tg.MessageActionChatDeletePhoto:
|
||||||
t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatInfoChange{
|
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
|
var avatar *bridgev2.Avatar
|
||||||
if photo, ok := channel.GetPhoto().(*tg.ChatPhoto); ok {
|
if photo, ok := channel.GetPhoto().(*tg.ChatPhoto); ok {
|
||||||
avatar = &bridgev2.Avatar{
|
avatar, err = t.convertChatPhoto(ctx, channel.ID, channel.AccessHash, photo)
|
||||||
ID: ids.MakeAvatarID(photo.PhotoID),
|
if err != nil {
|
||||||
Get: func(ctx context.Context) (data []byte, err error) {
|
return nil, err
|
||||||
return media.NewTransferer(t.client.API()).WithChannelPhoto(channel.ID, channel.AccessHash, photo.PhotoID).DownloadBytes(ctx)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user