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