github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/service/contacts.go (about)

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package service
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/contacts"
    12  	"github.com/keybase/client/go/libkb"
    13  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/keybase/client/go/uidmap"
    15  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    16  	"golang.org/x/net/context"
    17  )
    18  
    19  type bulkLookupContactsProvider struct{}
    20  
    21  var _ contacts.ContactsProvider = (*bulkLookupContactsProvider)(nil)
    22  
    23  func (c *bulkLookupContactsProvider) LookupAllWithToken(mctx libkb.MetaContext, emails []keybase1.EmailAddress,
    24  	numbers []keybase1.RawPhoneNumber, token contacts.Token) (contacts.ContactLookupResults, error) {
    25  	defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#LookupAllWithToken(len=%d)", len(emails)+len(numbers)),
    26  		nil)()
    27  	return contacts.BulkLookupContacts(mctx, emails, numbers, token)
    28  }
    29  
    30  func (c *bulkLookupContactsProvider) LookupAll(mctx libkb.MetaContext, emails []keybase1.EmailAddress,
    31  	numbers []keybase1.RawPhoneNumber) (contacts.ContactLookupResults, error) {
    32  	defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#LookupAll(len=%d)", len(emails)+len(numbers)),
    33  		nil)()
    34  	return c.LookupAllWithToken(mctx, emails, numbers, contacts.NoneToken)
    35  }
    36  
    37  func (c *bulkLookupContactsProvider) FindUsernames(mctx libkb.MetaContext,
    38  	uids []keybase1.UID) (res map[keybase1.UID]contacts.ContactUsernameAndFullName, err error) {
    39  	defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#FillUsernames(len=%d)", len(res)),
    40  		nil)()
    41  
    42  	const fullnameFreshness = 10 * time.Minute
    43  	const networkTimeBudget = uidmap.DefaultNetworkBudget
    44  	const forceNetworkForFullNames = true
    45  
    46  	nameMap, err := uidmap.MapUIDsReturnMapMctx(mctx, uids, fullnameFreshness, networkTimeBudget, forceNetworkForFullNames)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	res = make(map[keybase1.UID]contacts.ContactUsernameAndFullName)
    52  	for uid, v := range nameMap {
    53  		ufp := contacts.ContactUsernameAndFullName{
    54  			Username: v.NormalizedUsername.String(),
    55  		}
    56  		if fullNamePkg := v.FullName; fullNamePkg != nil {
    57  			ufp.Fullname = fullNamePkg.FullName.String()
    58  		}
    59  		res[uid] = ufp
    60  	}
    61  	return res, nil
    62  }
    63  
    64  func (c *bulkLookupContactsProvider) FindFollowing(mctx libkb.MetaContext,
    65  	uids []keybase1.UID) (res map[keybase1.UID]bool, err error) {
    66  	defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#FillFollowing(len=%d)", len(res)),
    67  		nil)()
    68  
    69  	arg := libkb.NewLoadUserArgWithMetaContext(mctx).WithSelf(true).WithStubMode(libkb.StubModeUnstubbed)
    70  	err = mctx.G().GetFullSelfer().WithUser(arg, func(user *libkb.User) error {
    71  		mctx.Debug("In WithUser: user found: %t", user != nil)
    72  		if user == nil {
    73  			return libkb.UserNotFoundError{}
    74  		}
    75  
    76  		var trackList []*libkb.TrackChainLink
    77  		idTable := user.IDTable()
    78  		if idTable != nil {
    79  			trackList = idTable.GetTrackList()
    80  		}
    81  
    82  		mctx.Debug("In WithUser: idTable exists: %t, trackList len: %d", idTable != nil, len(trackList))
    83  		res = make(map[keybase1.UID]bool)
    84  		if len(trackList) == 0 {
    85  			// Nothing to do.
    86  			return nil
    87  		}
    88  
    89  		followedUIDSet := make(map[keybase1.UID]struct{}, len(trackList))
    90  		for _, track := range trackList {
    91  			uid, err := track.GetTrackedUID()
    92  			if err != nil {
    93  				return err
    94  			}
    95  
    96  			followedUIDSet[uid] = struct{}{}
    97  		}
    98  
    99  		for _, v := range uids {
   100  			_, found := followedUIDSet[v]
   101  			res[v] = found
   102  		}
   103  
   104  		return nil
   105  	})
   106  
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return res, nil
   111  }
   112  
   113  func (c *bulkLookupContactsProvider) FindServiceMaps(mctx libkb.MetaContext,
   114  	uids []keybase1.UID) (res map[keybase1.UID]libkb.UserServiceSummary, err error) {
   115  	defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#FindServiceMaps(len=%d)", len(uids)),
   116  		&err)()
   117  
   118  	const serviceMapFreshness = 12 * time.Hour
   119  	const networkTimeBudget = uidmap.DefaultNetworkBudget
   120  	pkgs := mctx.G().ServiceMapper.MapUIDsToServiceSummaries(mctx.Ctx(), mctx.G(),
   121  		uids, serviceMapFreshness, networkTimeBudget)
   122  	res = make(map[keybase1.UID]libkb.UserServiceSummary, len(pkgs))
   123  	for uid, pkg := range pkgs {
   124  		if pkg.ServiceMap != nil {
   125  			res[uid] = pkg.ServiceMap
   126  		}
   127  	}
   128  	return res, nil
   129  }
   130  
   131  type ContactsHandler struct {
   132  	libkb.Contextified
   133  	*BaseHandler
   134  
   135  	contactsProvider *contacts.CachedContactsProvider
   136  }
   137  
   138  func NewCachedContactsProvider(g *libkb.GlobalContext) *contacts.CachedContactsProvider {
   139  	return &contacts.CachedContactsProvider{
   140  		Provider: &bulkLookupContactsProvider{},
   141  		Store:    contacts.NewContactCacheStore(g),
   142  	}
   143  }
   144  
   145  func NewContactsHandler(xp rpc.Transporter, g *libkb.GlobalContext, provider *contacts.CachedContactsProvider) *ContactsHandler {
   146  	handler := &ContactsHandler{
   147  		Contextified:     libkb.NewContextified(g),
   148  		BaseHandler:      NewBaseHandler(g, xp),
   149  		contactsProvider: provider,
   150  	}
   151  	return handler
   152  }
   153  
   154  var _ keybase1.ContactsInterface = (*ContactsHandler)(nil)
   155  
   156  func (h *ContactsHandler) LookupContactList(ctx context.Context, arg keybase1.LookupContactListArg) (res []keybase1.ProcessedContact, err error) {
   157  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("LOOKCON")
   158  	defer mctx.Trace(fmt.Sprintf("ContactsHandler#LookupContactList(len=%d)", len(arg.Contacts)),
   159  		&err)()
   160  	return contacts.ResolveContacts(mctx, h.contactsProvider, arg.Contacts)
   161  }
   162  
   163  func (h *ContactsHandler) SaveContactList(ctx context.Context, arg keybase1.SaveContactListArg) (res keybase1.ContactListResolutionResult, err error) {
   164  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("SAVECON")
   165  	defer mctx.Trace(fmt.Sprintf("ContactsHandler#SaveContactList(len=%d)", len(arg.Contacts)),
   166  		&err)()
   167  	return contacts.ResolveAndSaveContacts(mctx, h.contactsProvider, arg.Contacts)
   168  }
   169  
   170  func (h *ContactsHandler) LookupSavedContactsList(ctx context.Context, sessionID int) (res []keybase1.ProcessedContact, err error) {
   171  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("LOADCON")
   172  	defer mctx.Trace("ContactsHandler#LookupSavedContactsList", &err)()
   173  
   174  	store := h.G().SyncedContactList
   175  	savedContacts, err := store.RetrieveContacts(mctx)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	return savedContacts, nil
   180  }
   181  
   182  func (h *ContactsHandler) GetContactsForUserRecommendations(ctx context.Context, sessionID int) (res []keybase1.ProcessedContact, err error) {
   183  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("RECSCON")
   184  	defer mctx.Trace("ContactsHandler#GetContactsForUserRecommendations", &err)()
   185  
   186  	savedContacts, err := h.G().SyncedContactList.RetrieveContacts(mctx)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	// Allocate space for the number of all saved contacts - we will likely
   192  	// return less, though.
   193  	res = make([]keybase1.ProcessedContact, 0, len(savedContacts))
   194  
   195  	// Find contacts that have at least one resolved component, we are going to
   196  	// take only the resolved component from them (chose one if there are
   197  	// multiple).
   198  	seenResolvedContacts := make(map[int]struct{})
   199  	for _, contact := range savedContacts {
   200  		if contact.Resolved {
   201  			seenResolvedContacts[contact.ContactIndex] = struct{}{}
   202  		}
   203  	}
   204  
   205  	// Find the best contact for each resolved username.
   206  	// Map usernames to index in `res` list.
   207  	contactForUsername := make(map[string]int, len(seenResolvedContacts))
   208  	currentUID := mctx.CurrentUID()
   209  
   210  	for _, contact := range savedContacts {
   211  		if !contact.Resolved {
   212  			if _, found := seenResolvedContacts[contact.ContactIndex]; found {
   213  				// This contact has a resolved component, skip unresolved ones
   214  				// completely.
   215  				continue
   216  			}
   217  
   218  			res = append(res, contact)
   219  		} else {
   220  			if contact.Uid.Equal(currentUID) {
   221  				// Some people have their phone number in contact list, do not
   222  				// show current user in recommendations.
   223  				continue
   224  			}
   225  
   226  			if currentIndex, found := contactForUsername[contact.Username]; found {
   227  				current := res[currentIndex]
   228  				var overwrite bool
   229  				// NOTE: add more rules here if needed.
   230  				if current.Component.Email == nil && contact.Component.Email != nil {
   231  					// Prefer email components to phone ones.
   232  					overwrite = true
   233  				}
   234  
   235  				if overwrite {
   236  					res[currentIndex] = contact
   237  				}
   238  			} else {
   239  				contactForUsername[contact.Username] = len(res)
   240  				res = append(res, contact)
   241  			}
   242  		}
   243  	}
   244  
   245  	sort.Slice(res, func(i, j int) bool {
   246  		return res[i].DisplayName < res[j].DisplayName
   247  	})
   248  
   249  	return res, nil
   250  }