package peers import ( "context" "github.com/go-faster/errors" "go.uber.org/multierr" "go.mau.fi/mautrix-telegram/pkg/gotd/constant" "go.mau.fi/mautrix-telegram/pkg/gotd/tg" ) func (m *Manager) applyUsers(ctx context.Context, input ...tg.UserClass) error { var ( users []*tg.User ids = make([]constant.TDLibPeerID, 0, 16) ) if len(ids) < len(input) { ids = make([]constant.TDLibPeerID, 0, len(input)) } for _, user := range input { user, ok := user.(*tg.User) if !ok { // Got nil or Empty. continue } if user.Min { // TODO(tdakkota): call some hook to get actual user if got min (e.g. force gaps to getDifference) continue } users = append(users, user) id := user.GetID() k := Key{ Prefix: usersPrefix, ID: id, } v := Value{ AccessHash: user.AccessHash, } if err := m.storage.Save(ctx, k, v); err != nil { // FIXME(tdakkota): just log errors? return errors.Wrapf(err, "save user %d", user.ID) } if user.Phone != "" { if err := m.storage.SavePhone(ctx, user.Phone, k); err != nil { return errors.Wrapf(err, "save user %d", user.ID) } } ids = append(ids, userPeerID(id)) } if err := m.cache.SaveUsers(ctx, users...); err != nil { return errors.Wrap(err, "cache users") } m.updated(ids...) return nil } func (m *Manager) applyChats(ctx context.Context, input ...tg.ChatClass) error { var ( chats []*tg.Chat channels []*tg.Channel ids = make([]constant.TDLibPeerID, 0, 16) ) if len(ids) < len(input) { ids = make([]constant.TDLibPeerID, 0, len(input)) } for _, ch := range input { var ( k Key v Value ) // FIXME(tdakkota): check min constructors switch ch := ch.(type) { case *tg.Chat: k.ID = ch.ID k.Prefix = chatsPrefix chats = append(chats, ch) ids = append(ids, chatPeerID(ch.ID)) case *tg.ChatForbidden: k.ID = ch.ID k.Prefix = chatsPrefix case *tg.Channel: k.ID = ch.ID v.AccessHash = ch.AccessHash k.Prefix = channelPrefix channels = append(channels, ch) ids = append(ids, channelPeerID(ch.ID)) case *tg.ChannelForbidden: k.ID = ch.ID v.AccessHash = ch.AccessHash k.Prefix = channelPrefix default: // Got nil or Empty continue } if err := m.storage.Save(ctx, k, v); err != nil { // FIXME(tdakkota): just log errors? return errors.Wrapf(err, "save chat %d", k.ID) } } if err := m.cache.SaveChats(ctx, chats...); err != nil { return errors.Wrap(err, "cache chats") } if err := m.cache.SaveChannels(ctx, channels...); err != nil { return errors.Wrap(err, "cache channels") } m.updated(ids...) return nil } // Apply adds given entities to manager state. func (m *Manager) Apply(ctx context.Context, users []tg.UserClass, chats []tg.ChatClass) error { return m.applyEntities(ctx, users, chats) } func (m *Manager) applyEntities(ctx context.Context, users []tg.UserClass, chats []tg.ChatClass) error { return multierr.Append(m.applyUsers(ctx, users...), m.applyChats(ctx, chats...)) } func (m *Manager) applyFullUser(ctx context.Context, user *tg.UserFull) error { if user == nil { return nil } m.updatedFull(userPeerID(user.ID)) return m.cache.SaveUserFulls(ctx, user) } func (m *Manager) applyFullChat(ctx context.Context, chat *tg.ChatFull) error { if chat == nil { return nil } m.updatedFull(chatPeerID(chat.ID)) return m.cache.SaveChatFulls(ctx, chat) } func (m *Manager) applyFullChannel(ctx context.Context, ch *tg.ChannelFull) error { if ch == nil { return nil } m.updatedFull(channelPeerID(ch.ID)) return m.cache.SaveChannelFulls(ctx, ch) } func (m *Manager) updateContacts(ctx context.Context) ([]tg.UserClass, error) { ch := m.sg.DoChan("_contacts", func() (interface{}, error) { hash, err := m.storage.GetContactsHash(ctx) if err != nil { return nil, errors.Wrap(err, "get contacts hash") } r, err := m.api.ContactsGetContacts(ctx, hash) if err != nil { return nil, errors.Wrap(err, "get contacts") } switch c := r.(type) { case *tg.ContactsContacts: if err := m.applyUsers(ctx, c.Users...); err != nil { return nil, errors.Wrap(err, "update users") } myID, ok := m.myID() if !ok { return c.Users, nil } if err := m.storage.SaveContactsHash(ctx, contactsHash(myID, c)); err != nil { return nil, errors.Wrap(err, "update contacts hash") } return c.Users, nil case *tg.ContactsContactsNotModified: return nil, nil default: return nil, errors.Errorf("unexpected type %T", r) } }) select { case r := <-ch: if err := r.Err; err != nil { return nil, err } users, ok := r.Val.([]tg.UserClass) if !ok { return nil, nil } return users, nil case <-ctx.Done(): return nil, ctx.Err() } } type vectorHash struct { state uint64 } // See https://github.com/tdlib/td/blob/aa8a4979df8fc56032f134471a2cb939a7b0839f/td/telegram/misc.cpp#L242. func (h *vectorHash) apply(n uint64) { h.state ^= h.state >> 21 h.state ^= h.state << 35 h.state ^= h.state >> 4 h.state += n } // See https://github.com/tdlib/td/blob/aa8a4979df8fc56032f134471a2cb939a7b0839f/td/telegram/ContactsManager.cpp#L5125. func contactsHash(myID int64, contacts *tg.ContactsContacts) int64 { contacts.MapUsers().SortStableByID() var lesserIDx = len(contacts.Users) - 1 for i, user := range contacts.Users { if user.GetID() < myID { lesserIDx = i break } } var h vectorHash h.apply(uint64(len(contacts.Users))) for i, contact := range contacts.Users { h.apply(uint64(contact.GetID())) if i == lesserIDx { h.apply(uint64(myID)) } } return int64(h.state) }