From 58f40aeba5633480958726499ad00655047c91fe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Mar 2026 12:17:15 +0200 Subject: [PATCH] userinfo: use min access hashes for avatars --- pkg/connector/directdownload.go | 2 ++ pkg/connector/tomatrix.go | 24 ++++++++++++++++++------ pkg/connector/userinfo.go | 11 ++++++++++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/pkg/connector/directdownload.go b/pkg/connector/directdownload.go index 799c3f4d..36f2047b 100644 --- a/pkg/connector/directdownload.go +++ b/pkg/connector/directdownload.go @@ -181,6 +181,7 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med return nil, fmt.Errorf("unhandled media type %T", msgMedia) } } else if info.PeerType == ids.PeerTypeUser { + // TODO this needs to be able to use min access hashes 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) @@ -188,6 +189,7 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med } else if info.PeerType == ids.PeerTypeChat { readyTransferer = transferer.WithPeerPhoto(&tg.InputPeerChat{ChatID: info.PeerID}, info.ID) } else if info.PeerType == ids.PeerTypeChannel { + // TODO min access hashes here too readyTransferer, err = transferer.WithChannelPhoto(ctx, client.ScopedStore, info.PeerID, info.ID) if err != nil { return nil, err diff --git a/pkg/connector/tomatrix.go b/pkg/connector/tomatrix.go index d3285918..7272a19a 100644 --- a/pkg/connector/tomatrix.go +++ b/pkg/connector/tomatrix.go @@ -20,6 +20,7 @@ import ( "context" "crypto/sha256" "encoding/binary" + "errors" "fmt" "html" "strconv" @@ -37,6 +38,7 @@ import ( "go.mau.fi/mautrix-telegram/pkg/connector/ids" "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" "go.mau.fi/mautrix-telegram/pkg/connector/waveform" @@ -773,7 +775,7 @@ func convertGame(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { } } -func (c *TelegramClient) convertUserProfilePhoto(ctx context.Context, userID int64, photo *tg.UserProfilePhoto) (*bridgev2.Avatar, error) { +func (c *TelegramClient) convertUserProfilePhoto(ctx context.Context, user *tg.User, photo *tg.UserProfilePhoto) (*bridgev2.Avatar, error) { avatar := &bridgev2.Avatar{ ID: ids.MakeAvatarID(photo.PhotoID), } @@ -781,7 +783,7 @@ func (c *TelegramClient) convertUserProfilePhoto(ctx context.Context, userID int if c.main.useDirectMedia { mediaID, err := ids.DirectMediaInfo{ PeerType: ids.PeerTypeUser, - PeerID: userID, + PeerID: user.ID, UserID: c.telegramUserID, ID: photo.PhotoID, }.AsMediaID() @@ -795,11 +797,21 @@ func (c *TelegramClient) convertUserProfilePhoto(ctx context.Context, userID int 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 + // TODO determine if it's safe to unconditionally use the access hash from the user object here + peer, err := c.getInputPeerUser(ctx, user.ID) + if errors.Is(err, store.ErrNoAccessHash) { + peer = &tg.InputPeerUser{ + UserID: user.ID, + AccessHash: user.AccessHash, + } + if user.Min && c.metadata.IsBot { + // Bots should use a zero access hash when only a min hash is available + peer.AccessHash = 0 + } + } else if err != nil { + return nil, fmt.Errorf("failed to get peer: %w", err) } - return transferer.DownloadBytes(ctx) + return media.NewTransferer(c.client.API()).WithPeerPhoto(peer, photo.PhotoID).DownloadBytes(ctx) } } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index f6a8b7ec..6a761106 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -44,6 +44,14 @@ func (t *TelegramClient) getInputUser(ctx context.Context, id int64) (*tg.InputU return &tg.InputUser{UserID: id, AccessHash: accessHash}, nil } +func (t *TelegramClient) getInputPeerUser(ctx context.Context, id int64) (*tg.InputPeerUser, error) { + accessHash, err := t.ScopedStore.GetAccessHash(ctx, ids.PeerTypeUser, id) + if err != nil { + return nil, fmt.Errorf("failed to get access hash for user %d: %w", id, err) + } + return &tg.InputPeerUser{UserID: id, AccessHash: accessHash}, nil +} + func (t *TelegramClient) getSingleUser(ctx context.Context, id int64) (tg.UserClass, error) { if inputUser, err := t.getInputUser(ctx, id); err != nil { return nil, err @@ -131,10 +139,11 @@ func (t *TelegramClient) wrapUserInfo(ctx context.Context, u tg.UserClass) (*bri identifiers = slices.Compact(identifiers) var avatar *bridgev2.Avatar + // TODO don't apply min avatars without checking apply_min_photo or existing min status if p, ok := user.GetPhoto(); ok && p.TypeID() == tg.UserProfilePhotoTypeID { photo := p.(*tg.UserProfilePhoto) var err error - avatar, err = t.convertUserProfilePhoto(ctx, user.ID, photo) + avatar, err = t.convertUserProfilePhoto(ctx, user, photo) if err != nil { return nil, err }