1package chat 2 3import ( 4 "context" 5 "errors" 6 "sync" 7 8 "github.com/keybase/client/go/protocol/keybase1" 9 10 "github.com/keybase/client/go/chat/globals" 11 "github.com/keybase/client/go/chat/utils" 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/chat1" 14 "github.com/keybase/client/go/protocol/gregor1" 15) 16 17type teamMentionJob struct { 18 uid gregor1.UID 19 maybeMention chat1.MaybeMention 20 knownTeamMentions []chat1.KnownTeamMention 21 forceRemote bool 22} 23 24type TeamMentionLoader struct { 25 sync.Mutex 26 globals.Contextified 27 utils.DebugLabeler 28 29 started bool 30 jobCh chan teamMentionJob 31 shutdownCh chan chan struct{} 32} 33 34func NewTeamMentionLoader(g *globals.Context) *TeamMentionLoader { 35 return &TeamMentionLoader{ 36 Contextified: globals.NewContextified(g), 37 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "TeamMentionLoader", false), 38 jobCh: make(chan teamMentionJob, 100), 39 shutdownCh: make(chan chan struct{}, 5), 40 } 41} 42 43func (l *TeamMentionLoader) Start(ctx context.Context, uid gregor1.UID) { 44 defer l.Trace(ctx, nil, "Start")() 45 l.Lock() 46 defer l.Unlock() 47 if l.started { 48 return 49 } 50 l.started = true 51 go l.loadLoop() 52} 53 54func (l *TeamMentionLoader) Stop(ctx context.Context) chan struct{} { 55 defer l.Trace(ctx, nil, "Stop")() 56 l.Lock() 57 defer l.Unlock() 58 ch := make(chan struct{}) 59 if l.started { 60 l.shutdownCh <- ch 61 l.started = false 62 return ch 63 } 64 close(ch) 65 return ch 66} 67 68func (l *TeamMentionLoader) IsTeamMention(ctx context.Context, uid gregor1.UID, 69 maybeMention chat1.MaybeMention, knownTeamMentions []chat1.KnownTeamMention) bool { 70 teamName, err := keybase1.TeamNameFromString(maybeMention.Name) 71 if err != nil { 72 return false 73 } 74 name := teamName.String() 75 for _, known := range knownTeamMentions { 76 if known.Name == name { 77 return true 78 } 79 } 80 res, err := l.G().InboxSource.IsTeam(ctx, uid, name) 81 if err != nil { 82 l.Debug(ctx, "isTeam: failed to check if team: %s", err) 83 return false 84 } 85 return res 86} 87 88func (l *TeamMentionLoader) LoadTeamMention(ctx context.Context, uid gregor1.UID, 89 maybeMention chat1.MaybeMention, knownTeamMentions []chat1.KnownTeamMention, forceRemote bool) (err error) { 90 defer l.Trace(ctx, &err, "LoadTeamMention")() 91 select { 92 case l.jobCh <- teamMentionJob{ 93 uid: uid, 94 maybeMention: maybeMention, 95 knownTeamMentions: knownTeamMentions, 96 forceRemote: forceRemote, 97 }: 98 default: 99 l.Debug(ctx, "Load: failed to queue job, full") 100 return errors.New("queue full") 101 } 102 return nil 103} 104 105type mentionAPIResp struct { 106 Status libkb.AppStatus `json:"status"` 107 Name string 108 InTeam bool `json:"in_team"` 109 Open bool 110 Description string 111 PublicAdmins []string `json:"public_admins"` 112 NumMembers int `json:"num_members"` 113} 114 115func (r *mentionAPIResp) GetAppStatus() *libkb.AppStatus { 116 return &r.Status 117} 118 119func (l *TeamMentionLoader) getChatUI(ctx context.Context) (libkb.ChatUI, error) { 120 ui, err := l.G().UIRouter.GetChatUI() 121 if err != nil || ui == nil { 122 l.Debug(ctx, "getChatUI: no chat UI found: err: %s", err) 123 if err == nil { 124 err = errors.New("no chat UI found") 125 } 126 return nil, err 127 } 128 return ui, nil 129} 130 131func (l *TeamMentionLoader) loadMention(ctx context.Context, uid gregor1.UID, 132 maybeMention chat1.MaybeMention, knownTeamMentions []chat1.KnownTeamMention, 133 forceRemote bool) (err error) { 134 defer l.Trace(ctx, &err, "loadTeamMention: name: %s", maybeMention.Name)() 135 ui, err := l.getChatUI(ctx) 136 if err != nil { 137 return err 138 } 139 if _, err := keybase1.TeamNameFromString(maybeMention.Name); err != nil { 140 _ = ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel, 141 chat1.NewUIMaybeMentionInfoWithNothing()) 142 return errors.New("not a team string") 143 } 144 if !forceRemote && !l.IsTeamMention(ctx, uid, maybeMention, knownTeamMentions) { 145 _ = ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel, 146 chat1.NewUIMaybeMentionInfoWithUnknown()) 147 return errors.New("not a team mention") 148 } 149 150 var info chat1.UITeamMention 151 arg := libkb.APIArg{ 152 Endpoint: "team/mentiondesc", 153 SessionType: libkb.APISessionTypeREQUIRED, 154 Args: libkb.HTTPArgs{"name": libkb.S{Val: maybeMention.Name}}, 155 } 156 var resp mentionAPIResp 157 if err = l.G().API.GetDecode(libkb.NewMetaContext(ctx, l.G().ExternalG()), arg, &resp); err != nil { 158 l.Debug(ctx, "loadMention: failed to get team info: %s", err) 159 _ = ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel, 160 chat1.NewUIMaybeMentionInfoWithNothing()) 161 return err 162 } 163 info.Open = resp.Open 164 info.InTeam = resp.InTeam 165 if len(resp.Description) > 0 { 166 info.Description = new(string) 167 *info.Description = resp.Description 168 } 169 if resp.NumMembers > 0 { 170 info.NumMembers = new(int) 171 *info.NumMembers = resp.NumMembers 172 } 173 info.PublicAdmins = resp.PublicAdmins 174 175 if info.InTeam { 176 var channel *string 177 if len(maybeMention.Channel) > 0 { 178 channel = new(string) 179 *channel = maybeMention.Channel 180 } 181 convs, err := l.G().ChatHelper.FindConversations(ctx, maybeMention.Name, channel, 182 chat1.TopicType_CHAT, chat1.ConversationMembersType_TEAM, keybase1.TLFVisibility_PRIVATE) 183 if err != nil || len(convs) == 0 { 184 l.Debug(ctx, "loadMention: failed to find conversation: %v", err) 185 } else { 186 info.ConvID = new(chat1.ConvIDStr) 187 *info.ConvID = convs[0].GetConvID().ConvIDStr() 188 } 189 } 190 return ui.ChatMaybeMentionUpdate(ctx, maybeMention.Name, maybeMention.Channel, 191 chat1.NewUIMaybeMentionInfoWithTeam(info)) 192} 193 194func (l *TeamMentionLoader) loadLoop() { 195 ctx := context.Background() 196 for { 197 select { 198 case job := <-l.jobCh: 199 _ = l.loadMention(ctx, job.uid, job.maybeMention, job.knownTeamMentions, job.forceRemote) 200 case ch := <-l.shutdownCh: 201 close(ch) 202 return 203 } 204 } 205} 206