diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 68bec861..d3a9c1e1 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -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? diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 03b77ffa..bf64c0ee 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -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 } } diff --git a/pkg/connector/directdownload.go b/pkg/connector/directdownload.go index 54be79b4..d359963e 100644 --- a/pkg/connector/directdownload.go +++ b/pkg/connector/directdownload.go @@ -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) diff --git a/pkg/connector/ids/media.go b/pkg/connector/ids/media.go index 275d2f75..35d07761 100644 --- a/pkg/connector/ids/media.go +++ b/pkg/connector/ids/media.go @@ -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) } diff --git a/pkg/connector/media/transfer.go b/pkg/connector/media/transfer.go index 40d37fb1..0838126c 100644 --- a/pkg/connector/media/transfer.go +++ b/pkg/connector/media/transfer.go @@ -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 diff --git a/pkg/connector/sync.go b/pkg/connector/sync.go index fc1aa9ee..2a7eeb85 100644 --- a/pkg/connector/sync.go +++ b/pkg/connector/sync.go @@ -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()) }, diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 962ff653..391caa4c 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -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 } } diff --git a/pkg/connector/tomatrix.go b/pkg/connector/tomatrix.go index f17252f6..0cd93b79 100644 --- a/pkg/connector/tomatrix.go +++ b/pkg/connector/tomatrix.go @@ -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 +}