github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/interesting.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  
     7  	"github.com/keybase/client/go/libkb"
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  )
    10  
    11  type rankedList struct {
    12  	uids  []keybase1.UID
    13  	ranks map[string]int
    14  }
    15  
    16  func newRankedList(uids []keybase1.UID) *rankedList {
    17  	r := &rankedList{
    18  		uids:  uids,
    19  		ranks: make(map[string]int),
    20  	}
    21  	for index, uid := range uids {
    22  		r.ranks[uid.String()] = index
    23  	}
    24  	return r
    25  }
    26  
    27  func (r *rankedList) UIDs() []keybase1.UID {
    28  	return r.uids
    29  }
    30  
    31  func (r *rankedList) Rank(uid keybase1.UID) float64 {
    32  	var index int
    33  	var ok bool
    34  	if index, ok = r.ranks[uid.String()]; !ok {
    35  		return 0
    36  	}
    37  
    38  	total := float64(len(r.uids))
    39  	return ((total - float64(index)) / total)
    40  }
    41  
    42  type weightedRankedList struct {
    43  	*rankedList
    44  	weight float64
    45  }
    46  
    47  func newWeightedRankedList(rl *rankedList, weight float64) *weightedRankedList {
    48  	return &weightedRankedList{
    49  		rankedList: rl,
    50  		weight:     weight,
    51  	}
    52  }
    53  
    54  func (w *weightedRankedList) Weight() float64 {
    55  	return w.weight
    56  }
    57  
    58  type linearWeightedSelector struct {
    59  	libkb.Contextified
    60  	lists []*weightedRankedList
    61  }
    62  
    63  func newLinearWeightedSelector(g *libkb.GlobalContext, lists []*weightedRankedList) *linearWeightedSelector {
    64  	return &linearWeightedSelector{
    65  		Contextified: libkb.NewContextified(g),
    66  		lists:        lists,
    67  	}
    68  }
    69  
    70  func (w *linearWeightedSelector) getAllUIDs() (res []keybase1.UID) {
    71  	m := make(map[string]bool)
    72  	for _, list := range w.lists {
    73  		uids := list.UIDs()
    74  		for _, uid := range uids {
    75  			m[uid.String()] = true
    76  		}
    77  	}
    78  	for uid := range m {
    79  		res = append(res, keybase1.UID(uid))
    80  	}
    81  	return res
    82  }
    83  
    84  func (w *linearWeightedSelector) Select(maxUsers int) (res []keybase1.UID) {
    85  	type userScore struct {
    86  		uid   keybase1.UID
    87  		score float64
    88  	}
    89  	var scores []userScore
    90  	for _, uid := range w.getAllUIDs() {
    91  		total := 0.0
    92  		// Get score in each list to get a total score
    93  		for _, list := range w.lists {
    94  			total += list.Rank(uid) * list.Weight()
    95  		}
    96  		scores = append(scores, userScore{
    97  			uid:   uid,
    98  			score: total,
    99  		})
   100  	}
   101  
   102  	sort.Slice(scores, func(i, j int) bool { return scores[i].score > scores[j].score })
   103  	for _, score := range scores {
   104  		res = append(res, score.uid)
   105  		if len(res) > maxUsers {
   106  			break
   107  		}
   108  	}
   109  	return res
   110  }
   111  
   112  type interestingPeopleFn func(uid keybase1.UID) ([]keybase1.UID, error)
   113  
   114  type interestingPeopleSource struct {
   115  	fn     interestingPeopleFn
   116  	weight float64
   117  }
   118  
   119  type interestingPeople struct {
   120  	libkb.Contextified
   121  	sources []interestingPeopleSource
   122  }
   123  
   124  func newInterestingPeople(g *libkb.GlobalContext) *interestingPeople {
   125  	return &interestingPeople{
   126  		Contextified: libkb.NewContextified(g),
   127  	}
   128  }
   129  
   130  func (i *interestingPeople) AddSource(fn interestingPeopleFn, weight float64) {
   131  	i.sources = append(i.sources, interestingPeopleSource{
   132  		fn:     fn,
   133  		weight: weight,
   134  	})
   135  }
   136  
   137  func (i interestingPeople) Get(ctx context.Context, maxUsers int) ([]keybase1.UID, error) {
   138  	uid := i.G().Env.GetUID()
   139  	if uid.IsNil() {
   140  		return nil, libkb.LoginRequiredError{}
   141  	}
   142  
   143  	var weightedLists []*weightedRankedList
   144  	for _, source := range i.sources {
   145  		ppl, err := source.fn(uid)
   146  		if err != nil {
   147  			i.G().Log.Debug("interestingPeople: failed to get list from source: %s", err.Error())
   148  			return nil, err
   149  		}
   150  		weightedLists = append(weightedLists, newWeightedRankedList(newRankedList(ppl), source.weight))
   151  	}
   152  
   153  	return newLinearWeightedSelector(i.G(), weightedLists).Select(maxUsers), nil
   154  }