Files
mautrix-telegram/pkg/gotd/telegram/peers/apply.go
T
2025-06-27 20:03:37 -07:00

236 lines
5.5 KiB
Go

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)
}