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

     1  package chat
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"regexp"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/keybase/client/go/chat/attachments"
    18  	"github.com/keybase/client/go/chat/globals"
    19  	"github.com/keybase/client/go/chat/search"
    20  	"github.com/keybase/client/go/chat/storage"
    21  	"github.com/keybase/client/go/chat/types"
    22  	"github.com/keybase/client/go/chat/unfurl"
    23  	"github.com/keybase/client/go/chat/utils"
    24  	"github.com/keybase/client/go/libkb"
    25  	"github.com/keybase/client/go/protocol/chat1"
    26  	"github.com/keybase/client/go/protocol/gregor1"
    27  	"github.com/keybase/client/go/protocol/keybase1"
    28  	"github.com/keybase/client/go/teambot"
    29  	"github.com/keybase/client/go/teams"
    30  	"github.com/keybase/client/go/teams/opensearch"
    31  	"github.com/keybase/pipeliner"
    32  	"golang.org/x/net/context"
    33  	"golang.org/x/sync/errgroup"
    34  	"golang.org/x/sync/semaphore"
    35  )
    36  
    37  type UISource interface {
    38  	GetChatUI(sessionID int) libkb.ChatUI
    39  	GetStreamUICli() *keybase1.StreamUiClient
    40  }
    41  
    42  type Server struct {
    43  	globals.Contextified
    44  	utils.DebugLabeler
    45  
    46  	serverConn    types.ServerConnection
    47  	uiSource      UISource
    48  	boxer         *Boxer
    49  	identNotifier types.IdentifyNotifier
    50  
    51  	searchMu            sync.Mutex
    52  	searchInboxMu       sync.Mutex
    53  	loadGalleryMu       sync.Mutex
    54  	searchCancelFn      context.CancelFunc
    55  	searchInboxCancelFn context.CancelFunc
    56  	loadGalleryCancelFn context.CancelFunc
    57  
    58  	fileAttachmentDownloadConfigurationMu sync.RWMutex
    59  	fileAttachmentDownloadCacheDir        string
    60  	fileAttachmentDownloadDownloadDir     string
    61  
    62  	// Only for testing
    63  	rc         chat1.RemoteInterface
    64  	mockChatUI libkb.ChatUI
    65  }
    66  
    67  var _ chat1.LocalInterface = (*Server)(nil)
    68  
    69  func NewServer(g *globals.Context, serverConn types.ServerConnection, uiSource UISource) *Server {
    70  	return &Server{
    71  		Contextified:                      globals.NewContextified(g),
    72  		DebugLabeler:                      utils.NewDebugLabeler(g.ExternalG(), "Server", false),
    73  		serverConn:                        serverConn,
    74  		uiSource:                          uiSource,
    75  		boxer:                             NewBoxer(g),
    76  		identNotifier:                     NewCachingIdentifyNotifier(g),
    77  		fileAttachmentDownloadCacheDir:    g.GetEnv().GetCacheDir(),
    78  		fileAttachmentDownloadDownloadDir: g.GetEnv().GetDownloadsDir(),
    79  	}
    80  }
    81  
    82  func (h *Server) getChatUI(sessionID int) libkb.ChatUI {
    83  	if h.mockChatUI != nil {
    84  		return h.mockChatUI
    85  	}
    86  	return h.uiSource.GetChatUI(sessionID)
    87  }
    88  
    89  func (h *Server) getStreamUICli() *keybase1.StreamUiClient {
    90  	return h.uiSource.GetStreamUICli()
    91  }
    92  
    93  func (h *Server) shouldSquashError(err error) bool {
    94  	// these are not offline errors, but we never want the JS to receive them and potentially
    95  	// display a black bar
    96  	switch terr := err.(type) {
    97  	case storage.AbortedError:
    98  		return true
    99  	case TransientUnboxingError:
   100  		return h.shouldSquashError(terr.Inner())
   101  	}
   102  	switch err {
   103  	case utils.ErrConvLockTabDeadlock, context.Canceled:
   104  		return true
   105  	}
   106  	return false
   107  }
   108  
   109  func (h *Server) squashSquashableErrors(err error) error {
   110  	if h.shouldSquashError(err) {
   111  		return nil
   112  	}
   113  	return err
   114  }
   115  
   116  func (h *Server) handleOfflineError(ctx context.Context, err error,
   117  	res chat1.OfflinableResult) error {
   118  	if err == nil {
   119  		return nil
   120  	}
   121  	if h.shouldSquashError(err) {
   122  		return nil
   123  	}
   124  
   125  	errKind := IsOfflineError(err)
   126  	h.Debug(ctx, "handleOfflineError: errType: %T", err)
   127  	if errKind != OfflineErrorKindOnline {
   128  		h.Debug(ctx, "handleOfflineError: setting offline: err: %s", err)
   129  		res.SetOffline()
   130  		switch errKind {
   131  		case OfflineErrorKindOfflineReconnect:
   132  			// Reconnect Gregor if we think we are offline (and told to reconnect)
   133  			h.Debug(ctx, "handleOfflineError: reconnecting to gregor")
   134  			if _, err := h.serverConn.Reconnect(ctx); err != nil {
   135  				h.Debug(ctx, "handleOfflineError: error reconnecting: %s", err)
   136  			} else {
   137  				h.Debug(ctx, "handleOfflineError: success reconnecting")
   138  			}
   139  		default:
   140  			// Nothing to do for other errors.
   141  		}
   142  		return nil
   143  	}
   144  	return err
   145  }
   146  
   147  func (h *Server) setResultRateLimit(ctx context.Context, res types.RateLimitedResult) {
   148  	res.SetRateLimits(globals.CtxRateLimits(ctx))
   149  }
   150  
   151  func (h *Server) suspendBgConvLoads(ctx context.Context) func() {
   152  	return utils.SuspendComponents(ctx, h.G(), []types.Suspendable{
   153  		h.G().ConvLoader,
   154  		h.G().Indexer,
   155  	})
   156  }
   157  
   158  func (h *Server) suspendInboxSource(ctx context.Context) func() {
   159  	return utils.SuspendComponent(ctx, h.G(), h.G().InboxSource)
   160  }
   161  
   162  func (h *Server) RequestInboxLayout(ctx context.Context, reselectMode chat1.InboxLayoutReselectMode) (err error) {
   163  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
   164  	defer h.Trace(ctx, &err, "RequestInboxLayout")()
   165  	h.G().UIInboxLoader.UpdateLayout(ctx, reselectMode, "UI request")
   166  	return nil
   167  }
   168  
   169  func (h *Server) RequestInboxUnbox(ctx context.Context, convIDs []chat1.ConversationID) (err error) {
   170  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
   171  	ctx = globals.CtxAddLocalizerCancelable(ctx)
   172  	defer h.Trace(ctx, &err, "RequestInboxUnbox")()
   173  	defer h.PerfTrace(ctx, &err, "RequestInboxUnbox")()
   174  	for _, convID := range convIDs {
   175  		h.GetPerfLog().CDebugf(ctx, "RequestInboxUnbox: queuing unbox for: %s", convID)
   176  		h.Debug(ctx, "RequestInboxUnbox: queuing unbox for: %s", convID)
   177  	}
   178  	if err := h.G().UIInboxLoader.UpdateConvs(ctx, convIDs); err != nil {
   179  		h.Debug(ctx, "RequestInboxUnbox: failed to update convs: %s", err)
   180  	}
   181  	return nil
   182  }
   183  
   184  func (h *Server) RequestInboxSmallIncrease(ctx context.Context) (err error) {
   185  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
   186  	defer h.Trace(ctx, &err, "RequestInboxSmallIncrease")()
   187  	h.G().UIInboxLoader.UpdateLayoutFromSmallIncrease(ctx)
   188  	return nil
   189  }
   190  
   191  func (h *Server) RequestInboxSmallReset(ctx context.Context) (err error) {
   192  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
   193  	defer h.Trace(ctx, &err, "RequestInboxSmallReset")()
   194  	h.G().UIInboxLoader.UpdateLayoutFromSmallReset(ctx)
   195  	return nil
   196  }
   197  
   198  func (h *Server) GetInboxNonblockLocal(ctx context.Context, arg chat1.GetInboxNonblockLocalArg) (res chat1.NonblockFetchRes, err error) {
   199  	var breaks []keybase1.TLFIdentifyFailure
   200  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &breaks, h.identNotifier)
   201  	ctx = globals.CtxAddLocalizerCancelable(ctx)
   202  	defer h.Trace(ctx, &err, "GetInboxNonblockLocal")()
   203  	defer h.PerfTrace(ctx, &err, "GetInboxNonblockLocal")()
   204  	defer func() { h.setResultRateLimit(ctx, &res) }()
   205  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   206  	defer func() {
   207  		if res.Offline {
   208  			h.Debug(ctx, "GetInboxNonblockLocal: result obtained offline")
   209  		}
   210  	}()
   211  	defer h.suspendBgConvLoads(ctx)()
   212  
   213  	if err := h.G().UIInboxLoader.LoadNonblock(ctx, arg.Query, arg.MaxUnbox,
   214  		arg.SkipUnverified); err != nil {
   215  		return res, err
   216  	}
   217  	res.Offline = h.G().InboxSource.IsOffline(ctx)
   218  	res.IdentifyFailures = breaks
   219  	return res, nil
   220  }
   221  
   222  func (h *Server) MarkAsReadLocal(ctx context.Context, arg chat1.MarkAsReadLocalArg) (res chat1.MarkAsReadLocalRes, err error) {
   223  	var identBreaks []keybase1.TLFIdentifyFailure
   224  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks,
   225  		h.identNotifier)
   226  	defer h.Trace(ctx, &err,
   227  		fmt.Sprintf("MarkAsReadLocal(%s, %v)", arg.ConversationID, arg.MsgID))()
   228  	defer func() { h.setResultRateLimit(ctx, &res) }()
   229  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   230  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   231  	if err != nil {
   232  		h.Debug(ctx, "MarkAsRead: not logged in: %s", err)
   233  		return chat1.MarkAsReadLocalRes{}, nil
   234  	}
   235  	// Don't send remote mark as read if we somehow get this in the background.
   236  	if h.G().MobileAppState.State() != keybase1.MobileAppState_FOREGROUND {
   237  		h.Debug(ctx, "MarkAsReadLocal: not marking as read, app state not foreground: %v",
   238  			h.G().MobileAppState.State())
   239  		return chat1.MarkAsReadLocalRes{
   240  			Offline: h.G().InboxSource.IsOffline(ctx),
   241  		}, nil
   242  	}
   243  	if err = h.G().InboxSource.MarkAsRead(ctx, arg.ConversationID, uid, arg.MsgID, arg.ForceUnread); err != nil {
   244  		switch err {
   245  		case utils.ErrGetUnverifiedConvNotFound, utils.ErrGetVerifiedConvNotFound:
   246  			// if we couldn't find the conv, then just act like it worked
   247  		default:
   248  			return res, err
   249  		}
   250  	}
   251  	return chat1.MarkAsReadLocalRes{
   252  		Offline: h.G().InboxSource.IsOffline(ctx),
   253  	}, nil
   254  }
   255  
   256  func (h *Server) MarkTLFAsReadLocal(ctx context.Context, arg chat1.MarkTLFAsReadLocalArg) (res chat1.MarkTLFAsReadLocalRes, err error) {
   257  	var identBreaks []keybase1.TLFIdentifyFailure
   258  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, nil)
   259  	defer h.Trace(ctx, &err, "MarkTLFAsRead")()
   260  	defer func() { h.setResultRateLimit(ctx, &res) }()
   261  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   262  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   263  	if err != nil {
   264  		return res, err
   265  	}
   266  	convs, err := h.G().TeamChannelSource.GetChannelsFull(ctx, uid, arg.TlfID, chat1.TopicType_CHAT)
   267  	if err != nil {
   268  		return res, err
   269  	}
   270  	epick := libkb.FirstErrorPicker{}
   271  	for _, conv := range convs {
   272  		_, err = h.MarkAsReadLocal(ctx, chat1.MarkAsReadLocalArg{
   273  			ConversationID: conv.GetConvID(),
   274  			MsgID:          &conv.ReaderInfo.MaxMsgid,
   275  		})
   276  		epick.Push(err)
   277  	}
   278  	return chat1.MarkTLFAsReadLocalRes{
   279  		Offline: h.G().InboxSource.IsOffline(ctx),
   280  	}, epick.Error()
   281  }
   282  
   283  // GetInboxAndUnboxLocal implements keybase.chatLocal.getInboxAndUnboxLocal protocol.
   284  func (h *Server) GetInboxAndUnboxLocal(ctx context.Context, arg chat1.GetInboxAndUnboxLocalArg) (res chat1.GetInboxAndUnboxLocalRes, err error) {
   285  	var identBreaks []keybase1.TLFIdentifyFailure
   286  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   287  	if arg.Query != nil && arg.Query.TopicType != nil && *arg.Query.TopicType != chat1.TopicType_CHAT {
   288  		// make this cancelable for things like KBFS file edit convs
   289  		ctx = globals.CtxAddLocalizerCancelable(ctx)
   290  	}
   291  	defer h.Trace(ctx, &err, "GetInboxAndUnboxLocal")()
   292  	defer func() { h.setResultRateLimit(ctx, &res) }()
   293  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   294  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   295  	if err != nil {
   296  		return res, err
   297  	}
   298  	// Ignore these requests on mobile
   299  	if h.G().IsMobileAppType() && arg.Query != nil && arg.Query.TopicType != nil &&
   300  		*arg.Query.TopicType == chat1.TopicType_KBFSFILEEDIT {
   301  		return chat1.GetInboxAndUnboxLocalRes{
   302  			IdentifyFailures: identBreaks,
   303  		}, nil
   304  	}
   305  
   306  	// Read inbox from the source
   307  	ib, _, err := h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking,
   308  		types.InboxSourceDataSourceAll, nil, arg.Query)
   309  	switch err.(type) {
   310  	case nil:
   311  	case UnknownTLFNameError:
   312  		h.Debug(ctx, "GetInboxAndUnboxLocal: got unknown TLF name error, returning blank results")
   313  		ib.Convs = nil
   314  	default:
   315  		return res, err
   316  	}
   317  
   318  	return chat1.GetInboxAndUnboxLocalRes{
   319  		Conversations:    ib.Convs,
   320  		Offline:          h.G().InboxSource.IsOffline(ctx),
   321  		IdentifyFailures: identBreaks,
   322  	}, nil
   323  }
   324  
   325  func (h *Server) GetInboxAndUnboxUILocal(ctx context.Context, arg chat1.GetInboxAndUnboxUILocalArg) (res chat1.GetInboxAndUnboxUILocalRes, err error) {
   326  	var identBreaks []keybase1.TLFIdentifyFailure
   327  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   328  	defer h.Trace(ctx, &err, "GetInboxAndUnboxUILocal")()
   329  	defer func() { h.setResultRateLimit(ctx, &res) }()
   330  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   331  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   332  	if err != nil {
   333  		return res, err
   334  	}
   335  	// Read inbox from the source
   336  	ib, _, err := h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking,
   337  		types.InboxSourceDataSourceAll, nil, arg.Query)
   338  	switch err.(type) {
   339  	case nil:
   340  	case UnknownTLFNameError:
   341  		h.Debug(ctx, "GetInboxAndUnboxUILocal: got unknown TLF name error, returning blank results")
   342  		ib.Convs = nil
   343  	default:
   344  		return res, err
   345  	}
   346  	return chat1.GetInboxAndUnboxUILocalRes{
   347  		Conversations: utils.PresentConversationLocals(ctx, h.G(), uid, ib.Convs,
   348  			utils.PresentParticipantsModeInclude),
   349  		IdentifyFailures: identBreaks,
   350  	}, nil
   351  }
   352  
   353  // GetThreadLocal implements keybase.chatLocal.getThreadLocal protocol.
   354  func (h *Server) GetThreadLocal(ctx context.Context, arg chat1.GetThreadLocalArg) (res chat1.GetThreadLocalRes, err error) {
   355  	var identBreaks []keybase1.TLFIdentifyFailure
   356  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   357  	defer h.Trace(ctx, &err, "GetThreadLocal")()
   358  	defer func() { h.setResultRateLimit(ctx, &res) }()
   359  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   360  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   361  	if err != nil {
   362  		return chat1.GetThreadLocalRes{}, err
   363  	}
   364  	thread, err := h.G().UIThreadLoader.Load(ctx, uid, arg.ConversationID, arg.Reason, nil, arg.Query,
   365  		arg.Pagination)
   366  	if err != nil {
   367  		return chat1.GetThreadLocalRes{}, err
   368  	}
   369  	return chat1.GetThreadLocalRes{
   370  		Thread:           thread,
   371  		IdentifyFailures: identBreaks,
   372  	}, nil
   373  }
   374  
   375  func (h *Server) GetUnreadline(ctx context.Context, arg chat1.GetUnreadlineArg) (res chat1.UnreadlineRes, err error) {
   376  	var identBreaks []keybase1.TLFIdentifyFailure
   377  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   378  	defer h.Trace(ctx, &err,
   379  		fmt.Sprintf("GetUnreadline: convID: %v, readMsgID: %v", arg.ConvID, arg.ReadMsgID))()
   380  	defer func() { h.setResultRateLimit(ctx, &res) }()
   381  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   382  
   383  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   384  	if err != nil {
   385  		return res, err
   386  	}
   387  
   388  	res.UnreadlineID, err = h.G().ConvSource.GetUnreadline(ctx, arg.ConvID, uid, arg.ReadMsgID)
   389  	if err != nil {
   390  		h.Debug(ctx, "GetUnreadline: unable to run UnreadMsgID: %v", err)
   391  		return res, err
   392  	}
   393  	return res, nil
   394  }
   395  
   396  func (h *Server) GetThreadNonblock(ctx context.Context, arg chat1.GetThreadNonblockArg) (res chat1.NonblockFetchRes, err error) {
   397  	var identBreaks []keybase1.TLFIdentifyFailure
   398  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   399  	defer h.Trace(ctx, &err,
   400  		fmt.Sprintf("GetThreadNonblock(%s,%v,%v)", arg.ConversationID, arg.CbMode, arg.Reason))()
   401  	defer h.PerfTrace(ctx, &err,
   402  		fmt.Sprintf("GetThreadNonblock(%s,%v,%v)", arg.ConversationID, arg.CbMode, arg.Reason))()
   403  	defer func() { h.setResultRateLimit(ctx, &res) }()
   404  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   405  	defer h.suspendBgConvLoads(ctx)()
   406  	defer h.suspendInboxSource(ctx)()
   407  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   408  	if err != nil {
   409  		return chat1.NonblockFetchRes{}, err
   410  	}
   411  	chatUI := h.getChatUI(arg.SessionID)
   412  	return res, h.G().UIThreadLoader.LoadNonblock(ctx, chatUI, uid, arg.ConversationID, arg.Reason,
   413  		arg.Pgmode, arg.CbMode, arg.KnownRemotes, arg.Query, arg.Pagination)
   414  }
   415  
   416  func (h *Server) NewConversationsLocal(ctx context.Context, arg chat1.NewConversationsLocalArg) (res chat1.NewConversationsLocalRes, err error) {
   417  	var identBreaks []keybase1.TLFIdentifyFailure
   418  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   419  	defer h.Trace(ctx, &err, fmt.Sprintf("NewConversationsLocal(len=%d)", len(arg.NewConversationLocalArguments)))()
   420  	defer func() { h.setResultRateLimit(ctx, &res) }()
   421  
   422  	_, err = utils.AssertLoggedInUID(ctx, h.G())
   423  	if err != nil {
   424  		return chat1.NewConversationsLocalRes{}, err
   425  	}
   426  
   427  	var errs []error
   428  	for _, convArg := range arg.NewConversationLocalArguments {
   429  		var result chat1.NewConversationsLocalResult
   430  		newConvRes, err := h.NewConversationLocal(ctx, chat1.NewConversationLocalArg{
   431  			TlfName:          convArg.TlfName,
   432  			TopicType:        convArg.TopicType,
   433  			TlfVisibility:    convArg.TlfVisibility,
   434  			TopicName:        convArg.TopicName,
   435  			MembersType:      convArg.MembersType,
   436  			IdentifyBehavior: arg.IdentifyBehavior,
   437  		})
   438  		if err != nil {
   439  			e := err.Error()
   440  			result.Err = &e
   441  			errs = append(errs, err)
   442  		} else {
   443  			result.Result = new(chat1.NewConversationLocalRes)
   444  			result.Result.Conv = newConvRes.Conv
   445  			result.Result.UiConv = newConvRes.UiConv
   446  		}
   447  		res.Results = append(res.Results, result)
   448  	}
   449  
   450  	res.IdentifyFailures = identBreaks
   451  	return res, libkb.CombineErrors(errs...)
   452  }
   453  
   454  // NewConversationLocal implements keybase.chatLocal.newConversationLocal protocol.
   455  // Create a new conversation. Or in the case of CHAT, create-or-get a conversation.
   456  func (h *Server) NewConversationLocal(ctx context.Context, arg chat1.NewConversationLocalArg) (res chat1.NewConversationLocalRes, err error) {
   457  	var identBreaks []keybase1.TLFIdentifyFailure
   458  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   459  	defer h.Trace(ctx, &err,
   460  		fmt.Sprintf("NewConversationLocal(%s|%v)", arg.TlfName, arg.MembersType))()
   461  	defer func() { h.setResultRateLimit(ctx, &res) }()
   462  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   463  	if err != nil {
   464  		return chat1.NewConversationLocalRes{}, err
   465  	}
   466  
   467  	conv, created, err := NewConversation(ctx, h.G(), uid, arg.TlfName, arg.TopicName,
   468  		arg.TopicType, arg.MembersType, arg.TlfVisibility, nil, h.remoteClient, NewConvFindExistingNormal)
   469  	if err != nil {
   470  		h.Debug(ctx, "NewConversationLocal: failed to make conv: %s", err)
   471  		return res, err
   472  	}
   473  
   474  	res.Conv = conv
   475  	res.UiConv = utils.PresentConversationLocal(ctx, h.G(), uid, conv, utils.PresentParticipantsModeInclude)
   476  	res.IdentifyFailures = identBreaks
   477  
   478  	// If we are making a new channel in a team, send a system message to
   479  	// indicate this.
   480  	if created && arg.MembersType == chat1.ConversationMembersType_TEAM &&
   481  		arg.TopicType == chat1.TopicType_CHAT &&
   482  		arg.TopicName != nil && *arg.TopicName != globals.DefaultTeamTopic {
   483  		subBody := chat1.NewMessageSystemWithNewchannel(chat1.MessageSystemNewChannel{
   484  			Creator:        h.G().Env.GetUsername().String(),
   485  			NameAtCreation: *arg.TopicName,
   486  			ConvID:         conv.GetConvID(),
   487  		})
   488  		body := chat1.NewMessageBodyWithSystem(subBody)
   489  		err = h.G().ChatHelper.SendMsgByName(ctx, conv.Info.TlfName,
   490  			&globals.DefaultTeamTopic, arg.MembersType, keybase1.TLFIdentifyBehavior_CHAT_CLI,
   491  			body, chat1.MessageType_SYSTEM)
   492  		if err != nil {
   493  			h.Debug(ctx, "NewConversationLocal: unable to post new channel system message: %v", err)
   494  		}
   495  	}
   496  
   497  	// If we have this conv hidden, then let's bring it back before returning
   498  	if !utils.GetConversationStatusBehavior(conv.Info.Status).ShowInInbox {
   499  		h.Debug(ctx, "NewConversationLocal: new conversation not shown, unhiding: %s", conv.GetConvID())
   500  		if err := h.G().InboxSource.RemoteSetConversationStatus(ctx, uid, conv.GetConvID(),
   501  			chat1.ConversationStatus_UNFILED); err != nil {
   502  			h.Debug(ctx, "NewConversationLocal: unable to unhide conv: %s", err)
   503  			return res, err
   504  		}
   505  	}
   506  
   507  	return res, nil
   508  }
   509  
   510  func (h *Server) limitConvResults(ctx context.Context, uid gregor1.UID, allConvs []types.RemoteConversation,
   511  	num int) ([]chat1.ConversationLocal, error) {
   512  	var convs []types.RemoteConversation
   513  	sort.Sort(utils.RemoteConvByMtime(allConvs))
   514  	if len(allConvs) <= num {
   515  		convs = allConvs
   516  	} else {
   517  		convs = allConvs[:num]
   518  	}
   519  	locals, _, err := h.G().InboxSource.Localize(ctx, uid, convs, types.ConversationLocalizerBlocking)
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  	return locals, nil
   524  }
   525  
   526  func (h *Server) GetInboxSummaryForCLILocal(ctx context.Context, arg chat1.GetInboxSummaryForCLILocalQuery) (res chat1.GetInboxSummaryForCLILocalRes, err error) {
   527  	var identBreaks []keybase1.TLFIdentifyFailure
   528  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_CLI, &identBreaks,
   529  		h.identNotifier)
   530  	defer h.Trace(ctx, &err, "GetInboxSummaryForCLILocal")()
   531  	defer func() { h.setResultRateLimit(ctx, &res) }()
   532  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   533  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   534  	if err != nil {
   535  		return chat1.GetInboxSummaryForCLILocalRes{}, err
   536  	}
   537  
   538  	var after time.Time
   539  	if len(arg.After) > 0 {
   540  		after, err = utils.ParseTimeFromRFC3339OrDurationFromPast(h.G(), arg.After)
   541  		if err != nil {
   542  			return res, fmt.Errorf("parsing time or duration (%s) error: %s", arg.After, err)
   543  		}
   544  	}
   545  	var before time.Time
   546  	if len(arg.Before) > 0 {
   547  		before, err = utils.ParseTimeFromRFC3339OrDurationFromPast(h.G(), arg.Before)
   548  		if err != nil {
   549  			return res, fmt.Errorf("parsing time or duration (%s) error: %s", arg.Before, err)
   550  		}
   551  	}
   552  
   553  	var queryBase chat1.GetInboxQuery
   554  	queryBase.ComputeActiveList = true
   555  	queryBase.OneChatTypePerTLF = new(bool)
   556  	*queryBase.OneChatTypePerTLF = true
   557  	if !after.IsZero() {
   558  		gafter := gregor1.ToTime(after)
   559  		queryBase.After = &gafter
   560  	}
   561  	if !before.IsZero() {
   562  		gbefore := gregor1.ToTime(before)
   563  		queryBase.Before = &gbefore
   564  	}
   565  	if arg.TopicType != chat1.TopicType_NONE {
   566  		queryBase.TopicType = &arg.TopicType
   567  	}
   568  	if arg.Visibility != keybase1.TLFVisibility_ANY {
   569  		queryBase.TlfVisibility = &arg.Visibility
   570  	}
   571  	queryBase.Status = arg.Status
   572  	queryBase.ConvIDs = arg.ConvIDs
   573  
   574  	var gires chat1.GetInboxAndUnboxLocalRes
   575  	if arg.UnreadFirst {
   576  		if arg.UnreadFirstLimit.AtMost <= 0 {
   577  			arg.UnreadFirstLimit.AtMost = int(^uint(0) >> 1) // maximum int
   578  		}
   579  		if arg.UnreadFirstLimit.AtMost < arg.UnreadFirstLimit.AtLeast {
   580  			arg.UnreadFirstLimit.AtMost = arg.UnreadFirstLimit.AtLeast
   581  		}
   582  		query := queryBase
   583  		query.UnreadOnly, query.ReadOnly = true, false
   584  		ib, err := h.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceAll, &query)
   585  		if err != nil {
   586  			return chat1.GetInboxSummaryForCLILocalRes{}, err
   587  		}
   588  		res.Conversations, err = h.limitConvResults(ctx, uid, ib.ConvsUnverified,
   589  			arg.UnreadFirstLimit.AtMost)
   590  		if err != nil {
   591  			return chat1.GetInboxSummaryForCLILocalRes{}, err
   592  		}
   593  
   594  		more := utils.Collar(
   595  			arg.UnreadFirstLimit.AtLeast-len(res.Conversations),
   596  			arg.UnreadFirstLimit.NumRead,
   597  			arg.UnreadFirstLimit.AtMost-len(res.Conversations),
   598  		)
   599  		if more > 0 {
   600  			query := queryBase
   601  			query.UnreadOnly, query.ReadOnly = false, true
   602  			ib, err := h.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceAll, &query)
   603  			if err != nil {
   604  				return chat1.GetInboxSummaryForCLILocalRes{}, err
   605  			}
   606  			moreConvs, err := h.limitConvResults(ctx, uid, ib.ConvsUnverified, more)
   607  			if err != nil {
   608  				return chat1.GetInboxSummaryForCLILocalRes{}, err
   609  			}
   610  			res.Conversations = append(res.Conversations, moreConvs...)
   611  		}
   612  	} else {
   613  		if arg.ActivitySortedLimit <= 0 {
   614  			arg.ActivitySortedLimit = int(^uint(0) >> 1) // maximum int
   615  		}
   616  		query := queryBase
   617  		query.UnreadOnly, query.ReadOnly = false, false
   618  		ib, err := h.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceAll, &query)
   619  		if err != nil {
   620  			return chat1.GetInboxSummaryForCLILocalRes{}, err
   621  		}
   622  		res.Conversations, err = h.limitConvResults(ctx, uid, ib.ConvsUnverified, arg.ActivitySortedLimit)
   623  		if err != nil {
   624  			return chat1.GetInboxSummaryForCLILocalRes{}, err
   625  		}
   626  	}
   627  	res.Offline = gires.Offline
   628  	return res, nil
   629  }
   630  
   631  func (h *Server) GetConversationForCLILocal(ctx context.Context, arg chat1.GetConversationForCLILocalQuery) (res chat1.GetConversationForCLILocalRes, err error) {
   632  	var identBreaks []keybase1.TLFIdentifyFailure
   633  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_CLI, &identBreaks,
   634  		h.identNotifier)
   635  	defer h.Trace(ctx, &err, "GetConversationForCLILocal")()
   636  	defer func() { h.setResultRateLimit(ctx, &res) }()
   637  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   638  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
   639  		return res, err
   640  	}
   641  
   642  	if arg.Limit.AtMost <= 0 {
   643  		arg.Limit.AtMost = int(^uint(0) >> 1) // maximum int
   644  	}
   645  	if arg.Limit.AtMost < arg.Limit.AtLeast {
   646  		arg.Limit.AtMost = arg.Limit.AtLeast
   647  	}
   648  
   649  	convLocal := arg.Conv
   650  
   651  	var since time.Time
   652  	if arg.Since != nil {
   653  		since, err = utils.ParseTimeFromRFC3339OrDurationFromPast(h.G(), *arg.Since)
   654  		if err != nil {
   655  			return res, fmt.Errorf("parsing time or duration (%s) error: %s", *arg.Since, since)
   656  		}
   657  	}
   658  
   659  	query := chat1.GetThreadQuery{
   660  		MarkAsRead:   arg.MarkAsRead,
   661  		MessageTypes: arg.MessageTypes,
   662  	}
   663  	if !since.IsZero() {
   664  		gsince := gregor1.ToTime(since)
   665  		query.After = &gsince
   666  	}
   667  
   668  	tv, err := h.GetThreadLocal(ctx, chat1.GetThreadLocalArg{
   669  		ConversationID:   convLocal.Info.Id,
   670  		Query:            &query,
   671  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI,
   672  	})
   673  	if err != nil {
   674  		return chat1.GetConversationForCLILocalRes{}, err
   675  	}
   676  
   677  	// apply message count limits
   678  	var messages []chat1.MessageUnboxed
   679  	for _, m := range tv.Thread.Messages {
   680  		messages = append(messages, m)
   681  
   682  		arg.Limit.AtMost--
   683  		arg.Limit.AtLeast--
   684  		if m.GetMessageID() <= convLocal.ReaderInfo.ReadMsgid {
   685  			arg.Limit.NumRead--
   686  		}
   687  		if arg.Limit.AtMost <= 0 ||
   688  			(arg.Limit.NumRead <= 0 && arg.Limit.AtLeast <= 0) {
   689  			break
   690  		}
   691  	}
   692  
   693  	return chat1.GetConversationForCLILocalRes{
   694  		Conversation: convLocal,
   695  		Messages:     messages,
   696  		Offline:      tv.Offline,
   697  	}, nil
   698  }
   699  
   700  func (h *Server) GetMessagesLocal(ctx context.Context, arg chat1.GetMessagesLocalArg) (res chat1.GetMessagesLocalRes, err error) {
   701  	var identBreaks []keybase1.TLFIdentifyFailure
   702  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   703  	defer h.Trace(ctx, &err, "GetMessagesLocal")()
   704  	defer func() { h.setResultRateLimit(ctx, &res) }()
   705  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   706  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   707  	if err != nil {
   708  		return res, err
   709  	}
   710  	reason := chat1.GetThreadReason_GENERAL
   711  	messages, err := h.G().ChatHelper.GetMessages(ctx, uid, arg.ConversationID, arg.MessageIDs,
   712  		!arg.DisableResolveSupersedes, &reason)
   713  	if err != nil {
   714  		return res, err
   715  	}
   716  	return chat1.GetMessagesLocalRes{
   717  		Messages:         messages,
   718  		IdentifyFailures: identBreaks,
   719  	}, nil
   720  }
   721  
   722  func (h *Server) GetNextAttachmentMessageLocal(ctx context.Context,
   723  	arg chat1.GetNextAttachmentMessageLocalArg) (res chat1.GetNextAttachmentMessageLocalRes, err error) {
   724  	var identBreaks []keybase1.TLFIdentifyFailure
   725  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   726  	defer h.Trace(ctx, &err, "GetNextAttachmentMessageLocal(%s,%d,%v)",
   727  		arg.ConvID, arg.MessageID, arg.BackInTime)()
   728  	defer func() { h.setResultRateLimit(ctx, &res) }()
   729  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
   730  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   731  	if err != nil {
   732  		return res, err
   733  	}
   734  	gallery := attachments.NewGallery(h.G())
   735  	unboxed, _, err := gallery.NextMessage(ctx, uid, arg.ConvID, arg.MessageID,
   736  		attachments.NextMessageOptions{
   737  			BackInTime: arg.BackInTime,
   738  			AssetTypes: arg.AssetTypes,
   739  		},
   740  	)
   741  	if err != nil {
   742  		return res, err
   743  	}
   744  	var resMsg *chat1.UIMessage
   745  	if unboxed != nil {
   746  		resMsg = new(chat1.UIMessage)
   747  		*resMsg = utils.PresentMessageUnboxed(ctx, h.G(), *unboxed, uid, arg.ConvID)
   748  	}
   749  	return chat1.GetNextAttachmentMessageLocalRes{
   750  		Message:          resMsg,
   751  		IdentifyFailures: identBreaks,
   752  	}, nil
   753  }
   754  
   755  func (h *Server) SetConversationStatusLocal(ctx context.Context, arg chat1.SetConversationStatusLocalArg) (res chat1.SetConversationStatusLocalRes, err error) {
   756  	var identBreaks []keybase1.TLFIdentifyFailure
   757  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   758  	defer h.Trace(ctx, &err, fmt.Sprintf("SetConversationStatusLocal: %v", arg.Status))()
   759  	defer func() { h.setResultRateLimit(ctx, &res) }()
   760  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   761  	if err != nil {
   762  		return res, err
   763  	}
   764  	err = h.G().InboxSource.RemoteSetConversationStatus(ctx, uid, arg.ConversationID, arg.Status)
   765  	return chat1.SetConversationStatusLocalRes{
   766  		IdentifyFailures: identBreaks,
   767  	}, err
   768  }
   769  
   770  // PostLocal implements keybase.chatLocal.postLocal protocol.
   771  func (h *Server) PostLocal(ctx context.Context, arg chat1.PostLocalArg) (res chat1.PostLocalRes, err error) {
   772  	var identBreaks []keybase1.TLFIdentifyFailure
   773  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   774  	defer h.Trace(ctx, &err, "PostLocal")()
   775  	defer func() { h.setResultRateLimit(ctx, &res) }()
   776  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   777  	if err != nil {
   778  		return res, err
   779  	}
   780  
   781  	// Check for any slash command hits for an execute
   782  	if handled, err := h.G().CommandsSource.AttemptBuiltinCommand(ctx, uid, arg.ConversationID,
   783  		arg.Msg.ClientHeader.TlfName, arg.Msg.MessageBody, arg.ReplyTo); handled {
   784  		h.Debug(ctx, "PostLocal: handled slash command with error: %s", err)
   785  		return res, nil
   786  	}
   787  
   788  	// Run Stellar UI on any payments in the body
   789  	if !arg.SkipInChatPayments {
   790  		if arg.Msg.MessageBody, err = h.runStellarSendUI(ctx, arg.SessionID, uid, arg.ConversationID,
   791  			arg.Msg.MessageBody, arg.ReplyTo); err != nil {
   792  			return res, err
   793  		}
   794  	}
   795  
   796  	var prepareOpts chat1.SenderPrepareOptions
   797  	prepareOpts.ReplyTo = arg.ReplyTo
   798  	sender := NewBlockingSender(h.G(), h.boxer, h.remoteClient)
   799  	_, msgBoxed, err := sender.Send(ctx, arg.ConversationID, arg.Msg, 0, nil, nil, &prepareOpts)
   800  	if err != nil {
   801  		h.Debug(ctx, "PostLocal: unable to send message: %s", err.Error())
   802  		return res, err
   803  	}
   804  
   805  	return chat1.PostLocalRes{
   806  		MessageID:        msgBoxed.GetMessageID(),
   807  		IdentifyFailures: identBreaks,
   808  	}, nil
   809  }
   810  
   811  func (h *Server) PostDeleteNonblock(ctx context.Context, arg chat1.PostDeleteNonblockArg) (chat1.PostLocalNonblockRes, error) {
   812  	var parg chat1.PostLocalNonblockArg
   813  	parg.ClientPrev = arg.ClientPrev
   814  	parg.ConversationID = arg.ConversationID
   815  	parg.IdentifyBehavior = arg.IdentifyBehavior
   816  	parg.OutboxID = arg.OutboxID
   817  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_DELETE
   818  	parg.Msg.ClientHeader.Supersedes = arg.Supersedes
   819  	parg.Msg.ClientHeader.TlfName = arg.TlfName
   820  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
   821  
   822  	return h.PostLocalNonblock(ctx, parg)
   823  }
   824  
   825  func (h *Server) PostEditNonblock(ctx context.Context, arg chat1.PostEditNonblockArg) (res chat1.PostLocalNonblockRes, err error) {
   826  	var identBreaks []keybase1.TLFIdentifyFailure
   827  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
   828  	defer h.Trace(ctx, &err, "PostEditNonblock")()
   829  
   830  	var parg chat1.PostLocalNonblockArg
   831  	var supersedes chat1.MessageID
   832  	if arg.Target.MessageID != nil && *arg.Target.MessageID > 0 {
   833  		supersedes = *arg.Target.MessageID
   834  	}
   835  	if supersedes.IsNil() && arg.Target.OutboxID == nil {
   836  		return res, errors.New("must specify a messageID or outboxID for edit")
   837  	}
   838  	parg.ClientPrev = arg.ClientPrev
   839  	parg.ConversationID = arg.ConversationID
   840  	parg.IdentifyBehavior = arg.IdentifyBehavior
   841  	parg.OutboxID = arg.OutboxID
   842  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_EDIT
   843  	parg.Msg.ClientHeader.Supersedes = supersedes
   844  	parg.Msg.ClientHeader.TlfName = arg.TlfName
   845  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
   846  	parg.Msg.MessageBody = chat1.NewMessageBodyWithEdit(chat1.MessageEdit{
   847  		MessageID: supersedes,
   848  		Body:      arg.Body,
   849  	})
   850  	if supersedes.IsNil() {
   851  		h.Debug(ctx, "PostEditNonblock: setting supersedes outboxID: %s", arg.Target.OutboxID)
   852  		parg.Msg.SupersedesOutboxID = arg.Target.OutboxID
   853  	}
   854  	return h.PostLocalNonblock(ctx, parg)
   855  }
   856  
   857  func (h *Server) runStellarSendUI(ctx context.Context, sessionID int, uid gregor1.UID,
   858  	convID chat1.ConversationID, msgBody chat1.MessageBody, replyTo *chat1.MessageID) (res chat1.MessageBody, err error) {
   859  	defer h.Trace(ctx, &err, "runStellarSendUI")()
   860  	ui := h.getChatUI(sessionID)
   861  	bodyTyp, err := msgBody.MessageType()
   862  	if err != nil || bodyTyp != chat1.MessageType_TEXT {
   863  		return msgBody, nil
   864  	}
   865  	body := msgBody.Text().Body
   866  	parsedPayments := h.G().StellarSender.ParsePayments(ctx, uid, convID, body, replyTo)
   867  	if len(parsedPayments) == 0 {
   868  		h.Debug(ctx, "runStellarSendUI: no payments")
   869  		return msgBody, nil
   870  	}
   871  	h.Debug(ctx, "runStellarSendUI: payments found, showing confirm screen")
   872  	if err := ui.ChatStellarShowConfirm(ctx); err != nil {
   873  		return res, err
   874  	}
   875  	defer func() {
   876  		_ = ui.ChatStellarDone(ctx, err != nil)
   877  	}()
   878  	uiSummary, toSend, err := h.G().StellarSender.DescribePayments(ctx, uid, convID, parsedPayments)
   879  	if err != nil {
   880  		if err := libkb.ExportErrorAsStatus(h.G().GlobalContext, err); err != nil {
   881  			_, _ = ui.ChatStellarDataError(ctx, *err)
   882  		} else {
   883  			h.Debug(ctx, "error exported to nothing") // should never happen
   884  		}
   885  		return res, err
   886  	}
   887  	h.Debug(ctx, "runStellarSendUI: payments described, telling UI")
   888  	accepted, err := ui.ChatStellarDataConfirm(ctx, uiSummary)
   889  	if err != nil {
   890  		return res, err
   891  	}
   892  	if !accepted {
   893  		return res, errors.New("Payment message declined")
   894  	}
   895  	h.Debug(ctx, "runStellarSendUI: message confirmed, sending payments")
   896  	payments, err := h.G().StellarSender.SendPayments(ctx, convID, toSend)
   897  	if err != nil {
   898  		// Send regardless here
   899  		h.Debug(ctx, "runStellarSendUI: failed to send payments, but continuing on: %s", err)
   900  		return msgBody, nil
   901  	}
   902  	newBody := msgBody.Text().DeepCopy()
   903  	newBody.Body = body
   904  	newBody.Payments = payments
   905  	return chat1.NewMessageBodyWithText(newBody), nil
   906  }
   907  
   908  var quickReactionPattern = regexp.MustCompile(`(?:^\+:)([^\s]+)(?::)$`)
   909  
   910  func (h *Server) isQuickReaction(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
   911  	body string) (reaction string, msgID chat1.MessageID, ok bool) {
   912  	body = strings.TrimSpace(body)
   913  	if !(strings.HasPrefix(body, "+:") && strings.HasSuffix(body, ":")) {
   914  		return "", 0, false
   915  	}
   916  	hits := quickReactionPattern.FindStringSubmatch(body)
   917  	if len(hits) < 2 {
   918  		return "", 0, false
   919  	}
   920  	tryStock := func() (string, bool) {
   921  		if h.G().EmojiSource.IsStockEmoji(hits[1]) {
   922  			return ":" + hits[1] + ":", true
   923  		}
   924  		return "", false
   925  	}
   926  	tryCustom := func() (string, bool) {
   927  		emojis, err := h.G().EmojiSource.Harvest(ctx, body, uid, convID, types.EmojiHarvestModeFast)
   928  		if err != nil {
   929  			h.Debug(ctx, "isQuickReaction: failed to harvest: %s", err)
   930  			return "", false
   931  		}
   932  		if len(emojis) != 1 {
   933  			return "", false
   934  		}
   935  		return ":" + emojis[0].Alias + ":", true
   936  	}
   937  	var hit *string
   938  	if reaction, ok = tryStock(); ok {
   939  		hit = new(string)
   940  		*hit = reaction
   941  	}
   942  	if hit == nil {
   943  		if reaction, ok = tryCustom(); ok {
   944  			hit = new(string)
   945  			*hit = reaction
   946  		}
   947  	}
   948  	if hit == nil {
   949  		return "", 0, false
   950  	}
   951  	conv, err := utils.GetUnverifiedConv(ctx, h.G(), uid, convID, types.InboxSourceDataSourceLocalOnly)
   952  	if err != nil {
   953  		h.Debug(ctx, "isQuickReaction: failed to get conv: %s", err)
   954  		return "", 0, false
   955  	}
   956  	return *hit, conv.MaxVisibleMsgID(), true
   957  }
   958  
   959  func (h *Server) PostTextNonblock(ctx context.Context, arg chat1.PostTextNonblockArg) (res chat1.PostLocalNonblockRes, err error) {
   960  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, nil, h.identNotifier)
   961  	defer h.Trace(ctx, &err, "PostTextNonblock")()
   962  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
   963  	if err != nil {
   964  		return res, err
   965  	}
   966  	reaction, msgID, ok := h.isQuickReaction(ctx, uid, arg.ConversationID, arg.Body)
   967  	if ok {
   968  		h.Debug(ctx, "PostTextNonblock: detected quick reaction")
   969  		return h.PostReactionNonblock(ctx, chat1.PostReactionNonblockArg{
   970  			ConversationID:   arg.ConversationID,
   971  			TlfName:          arg.TlfName,
   972  			TlfPublic:        arg.TlfPublic,
   973  			Supersedes:       msgID,
   974  			Body:             reaction,
   975  			OutboxID:         arg.OutboxID,
   976  			ClientPrev:       arg.ClientPrev,
   977  			IdentifyBehavior: arg.IdentifyBehavior,
   978  		})
   979  	}
   980  
   981  	var parg chat1.PostLocalNonblockArg
   982  	parg.SessionID = arg.SessionID
   983  	parg.ClientPrev = arg.ClientPrev
   984  	parg.ConversationID = arg.ConversationID
   985  	parg.IdentifyBehavior = arg.IdentifyBehavior
   986  	parg.OutboxID = arg.OutboxID
   987  	parg.ReplyTo = arg.ReplyTo
   988  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_TEXT
   989  	parg.Msg.ClientHeader.TlfName = arg.TlfName
   990  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
   991  	parg.Msg.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
   992  		Body: arg.Body,
   993  	})
   994  	if arg.EphemeralLifetime != nil {
   995  		parg.Msg.ClientHeader.EphemeralMetadata = &chat1.MsgEphemeralMetadata{
   996  			Lifetime: *arg.EphemeralLifetime,
   997  		}
   998  	}
   999  	return h.PostLocalNonblock(ctx, parg)
  1000  }
  1001  
  1002  func (h *Server) PostReactionNonblock(ctx context.Context, arg chat1.PostReactionNonblockArg) (res chat1.PostLocalNonblockRes, err error) {
  1003  	var parg chat1.PostLocalNonblockArg
  1004  	parg.ClientPrev = arg.ClientPrev
  1005  	parg.ConversationID = arg.ConversationID
  1006  	parg.IdentifyBehavior = arg.IdentifyBehavior
  1007  	parg.OutboxID = arg.OutboxID
  1008  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_REACTION
  1009  	parg.Msg.ClientHeader.Supersedes = arg.Supersedes
  1010  	parg.Msg.ClientHeader.TlfName = arg.TlfName
  1011  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
  1012  	parg.Msg.MessageBody = chat1.NewMessageBodyWithReaction(chat1.MessageReaction{
  1013  		MessageID: arg.Supersedes,
  1014  		Body:      arg.Body,
  1015  	})
  1016  
  1017  	return h.PostLocalNonblock(ctx, parg)
  1018  }
  1019  
  1020  func (h *Server) PostHeadlineNonblock(ctx context.Context, arg chat1.PostHeadlineNonblockArg) (chat1.PostLocalNonblockRes, error) {
  1021  	var parg chat1.PostLocalNonblockArg
  1022  	parg.ClientPrev = arg.ClientPrev
  1023  	parg.ConversationID = arg.ConversationID
  1024  	parg.IdentifyBehavior = arg.IdentifyBehavior
  1025  	parg.OutboxID = arg.OutboxID
  1026  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_HEADLINE
  1027  	parg.Msg.ClientHeader.TlfName = arg.TlfName
  1028  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
  1029  	parg.Msg.MessageBody = chat1.NewMessageBodyWithHeadline(chat1.MessageHeadline{
  1030  		Headline: arg.Headline,
  1031  	})
  1032  
  1033  	return h.PostLocalNonblock(ctx, parg)
  1034  }
  1035  
  1036  func (h *Server) PostHeadline(ctx context.Context, arg chat1.PostHeadlineArg) (chat1.PostLocalRes, error) {
  1037  	var parg chat1.PostLocalArg
  1038  	parg.ConversationID = arg.ConversationID
  1039  	parg.IdentifyBehavior = arg.IdentifyBehavior
  1040  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_HEADLINE
  1041  	parg.Msg.ClientHeader.TlfName = arg.TlfName
  1042  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
  1043  	parg.Msg.MessageBody = chat1.NewMessageBodyWithHeadline(chat1.MessageHeadline{
  1044  		Headline: arg.Headline,
  1045  	})
  1046  
  1047  	return h.PostLocal(ctx, parg)
  1048  }
  1049  
  1050  func (h *Server) PostMetadataNonblock(ctx context.Context, arg chat1.PostMetadataNonblockArg) (chat1.PostLocalNonblockRes, error) {
  1051  	var parg chat1.PostLocalNonblockArg
  1052  	parg.ClientPrev = arg.ClientPrev
  1053  	parg.ConversationID = arg.ConversationID
  1054  	parg.IdentifyBehavior = arg.IdentifyBehavior
  1055  	parg.OutboxID = arg.OutboxID
  1056  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_METADATA
  1057  	parg.Msg.ClientHeader.TlfName = arg.TlfName
  1058  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
  1059  	parg.Msg.MessageBody = chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{
  1060  		ConversationTitle: arg.ChannelName,
  1061  	})
  1062  
  1063  	return h.PostLocalNonblock(ctx, parg)
  1064  }
  1065  
  1066  func (h *Server) PostMetadata(ctx context.Context, arg chat1.PostMetadataArg) (chat1.PostLocalRes, error) {
  1067  	var parg chat1.PostLocalArg
  1068  	parg.ConversationID = arg.ConversationID
  1069  	parg.IdentifyBehavior = arg.IdentifyBehavior
  1070  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_METADATA
  1071  	parg.Msg.ClientHeader.TlfName = arg.TlfName
  1072  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
  1073  	parg.Msg.MessageBody = chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{
  1074  		ConversationTitle: arg.ChannelName,
  1075  	})
  1076  
  1077  	return h.PostLocal(ctx, parg)
  1078  }
  1079  
  1080  func (h *Server) PostDeleteHistoryUpto(ctx context.Context, arg chat1.PostDeleteHistoryUptoArg) (res chat1.PostLocalRes, err error) {
  1081  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, nil, h.identNotifier)
  1082  	defer h.Trace(ctx, &err, "PostDeleteHistoryUpto")()
  1083  
  1084  	delh := chat1.MessageDeleteHistory{Upto: arg.Upto}
  1085  
  1086  	var parg chat1.PostLocalArg
  1087  	parg.ConversationID = arg.ConversationID
  1088  	parg.IdentifyBehavior = arg.IdentifyBehavior
  1089  	parg.Msg.ClientHeader.MessageType = chat1.MessageType_DELETEHISTORY
  1090  	parg.Msg.ClientHeader.TlfName = arg.TlfName
  1091  	parg.Msg.ClientHeader.TlfPublic = arg.TlfPublic
  1092  	parg.Msg.ClientHeader.DeleteHistory = &delh
  1093  	parg.Msg.MessageBody = chat1.NewMessageBodyWithDeletehistory(delh)
  1094  
  1095  	h.Debug(ctx, "PostDeleteHistoryUpto: deleting upto msgid:%v", delh.Upto)
  1096  
  1097  	return h.PostLocal(ctx, parg)
  1098  }
  1099  
  1100  func (h *Server) PostDeleteHistoryThrough(ctx context.Context, arg chat1.PostDeleteHistoryThroughArg) (res chat1.PostLocalRes, err error) {
  1101  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, nil, h.identNotifier)
  1102  	defer h.Trace(ctx, &err, "PostDeleteHistoryThrough")()
  1103  	return h.PostDeleteHistoryUpto(ctx, chat1.PostDeleteHistoryUptoArg{
  1104  		ConversationID:   arg.ConversationID,
  1105  		TlfName:          arg.TlfName,
  1106  		TlfPublic:        arg.TlfPublic,
  1107  		IdentifyBehavior: arg.IdentifyBehavior,
  1108  		Upto:             arg.Through + 1,
  1109  	})
  1110  }
  1111  
  1112  func (h *Server) PostDeleteHistoryByAge(ctx context.Context, arg chat1.PostDeleteHistoryByAgeArg) (res chat1.PostLocalRes, err error) {
  1113  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, nil, h.identNotifier)
  1114  	defer h.Trace(ctx, &err, "PostDeleteHistoryByAge")()
  1115  
  1116  	gmRes, err := h.remoteClient().GetMessageBefore(ctx, chat1.GetMessageBeforeArg{
  1117  		ConvID: arg.ConversationID,
  1118  		Age:    arg.Age,
  1119  	})
  1120  	if err != nil {
  1121  		return res, err
  1122  	}
  1123  	upto := gmRes.MsgID + 1
  1124  	h.Debug(ctx, "PostDeleteHistoryByAge: deleting upto msgid:%v (age:%v)", upto, arg.Age)
  1125  	return h.PostDeleteHistoryUpto(ctx, chat1.PostDeleteHistoryUptoArg{
  1126  		ConversationID:   arg.ConversationID,
  1127  		TlfName:          arg.TlfName,
  1128  		TlfPublic:        arg.TlfPublic,
  1129  		IdentifyBehavior: arg.IdentifyBehavior,
  1130  		Upto:             upto,
  1131  	})
  1132  }
  1133  
  1134  func (h *Server) GenerateOutboxID(ctx context.Context) (res chat1.OutboxID, err error) {
  1135  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil, h.identNotifier)
  1136  	defer h.Trace(ctx, &err, "GenerateOutboxID")()
  1137  	return storage.NewOutboxID()
  1138  }
  1139  
  1140  func (h *Server) PostLocalNonblock(ctx context.Context, arg chat1.PostLocalNonblockArg) (res chat1.PostLocalNonblockRes, err error) {
  1141  	var identBreaks []keybase1.TLFIdentifyFailure
  1142  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1143  	defer h.Trace(ctx, &err, "PostLocalNonblock")()
  1144  	defer h.suspendBgConvLoads(ctx)()
  1145  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1146  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1147  	if err != nil {
  1148  		return res, err
  1149  	}
  1150  
  1151  	// Clear draft
  1152  	if err := h.G().InboxSource.Draft(ctx, uid, arg.ConversationID, nil); err != nil {
  1153  		h.Debug(ctx, "PostLocalNonblock: failed to clear draft: %s", err)
  1154  	}
  1155  
  1156  	// Check for any slash command hits for an execute
  1157  	if handled, err := h.G().CommandsSource.AttemptBuiltinCommand(ctx, uid, arg.ConversationID,
  1158  		arg.Msg.ClientHeader.TlfName, arg.Msg.MessageBody, arg.ReplyTo); handled {
  1159  		h.Debug(ctx, "PostLocalNonblock: handled slash command with error: %s", err)
  1160  		return res, nil
  1161  	}
  1162  
  1163  	if !arg.SkipInChatPayments {
  1164  		// Determine if the messages contains any Stellar payments, and execute
  1165  		// them if so
  1166  		if arg.Msg.MessageBody, err = h.runStellarSendUI(ctx, arg.SessionID, uid, arg.ConversationID,
  1167  			arg.Msg.MessageBody, arg.ReplyTo); err != nil {
  1168  			return res, err
  1169  		}
  1170  	}
  1171  
  1172  	// Create non block sender
  1173  	var prepareOpts chat1.SenderPrepareOptions
  1174  	sender := NewBlockingSender(h.G(), h.boxer, h.remoteClient)
  1175  	nonblockSender := NewNonblockingSender(h.G(), sender)
  1176  	prepareOpts.ReplyTo = arg.ReplyTo
  1177  	if arg.Msg.ClientHeader.Conv.TopicType == chat1.TopicType_NONE {
  1178  		arg.Msg.ClientHeader.Conv.TopicType = chat1.TopicType_CHAT
  1179  	}
  1180  	obid, _, err := nonblockSender.Send(ctx, arg.ConversationID, arg.Msg, arg.ClientPrev, arg.OutboxID,
  1181  		nil, &prepareOpts)
  1182  	if err != nil {
  1183  		return res, fmt.Errorf("PostLocalNonblock: unable to send message: err: %s", err.Error())
  1184  	}
  1185  	h.Debug(ctx, "PostLocalNonblock: using outboxID: %s", obid)
  1186  
  1187  	return chat1.PostLocalNonblockRes{
  1188  		OutboxID:         obid,
  1189  		IdentifyFailures: identBreaks,
  1190  	}, nil
  1191  }
  1192  
  1193  // MakePreview implements chat1.LocalInterface.MakePreview.
  1194  func (h *Server) MakePreview(ctx context.Context, arg chat1.MakePreviewArg) (res chat1.MakePreviewRes, err error) {
  1195  	defer h.Trace(ctx, &err, "MakePreview")()
  1196  	return attachments.NewSender(h.G()).MakePreview(ctx, arg.Filename, arg.OutboxID)
  1197  }
  1198  
  1199  func (h *Server) MakeAudioPreview(ctx context.Context, arg chat1.MakeAudioPreviewArg) (res chat1.MakePreviewRes, err error) {
  1200  	defer h.Trace(ctx, &err, "MakeAudioPreview")()
  1201  	return attachments.NewSender(h.G()).MakeAudioPreview(ctx, arg.Amps, arg.Duration)
  1202  }
  1203  
  1204  func (h *Server) GetUploadTempFile(ctx context.Context, arg chat1.GetUploadTempFileArg) (res string, err error) {
  1205  	defer h.Trace(ctx, &err, "GetUploadTempFile")()
  1206  	return h.G().AttachmentUploader.GetUploadTempFile(ctx, arg.OutboxID, arg.Filename)
  1207  }
  1208  
  1209  func (h *Server) MakeUploadTempFile(ctx context.Context, arg chat1.MakeUploadTempFileArg) (res string, err error) {
  1210  	defer h.Trace(ctx, &err, "MakeUploadTempFile")()
  1211  	if res, err = h.G().AttachmentUploader.GetUploadTempFile(ctx, arg.OutboxID, arg.Filename); err != nil {
  1212  		return res, err
  1213  	}
  1214  	return res, os.WriteFile(res, arg.Data, 0644)
  1215  }
  1216  
  1217  func (h *Server) CancelUploadTempFile(ctx context.Context, outboxID chat1.OutboxID) (err error) {
  1218  	defer h.Trace(ctx, &err, "CancelUploadTempFile: %s", outboxID)()
  1219  	return h.G().AttachmentUploader.CancelUploadTempFile(ctx, outboxID)
  1220  }
  1221  
  1222  func (h *Server) PostFileAttachmentLocalNonblock(ctx context.Context,
  1223  	arg chat1.PostFileAttachmentLocalNonblockArg) (res chat1.PostLocalNonblockRes, err error) {
  1224  	var identBreaks []keybase1.TLFIdentifyFailure
  1225  	ctx = globals.ChatCtx(ctx, h.G(), arg.Arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1226  	defer h.Trace(ctx, &err, "PostFileAttachmentLocalNonblock")()
  1227  	defer h.suspendBgConvLoads(ctx)()
  1228  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1229  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1230  	if err != nil {
  1231  		return res, err
  1232  	}
  1233  
  1234  	// Create non block sender
  1235  	sender := NewNonblockingSender(h.G(), NewBlockingSender(h.G(), h.boxer, h.remoteClient))
  1236  	outboxID, _, err := attachments.NewSender(h.G()).PostFileAttachmentMessage(ctx, sender,
  1237  		arg.Arg.ConversationID, arg.Arg.TlfName, arg.Arg.Visibility, arg.Arg.OutboxID, arg.Arg.Filename,
  1238  		arg.Arg.Title, arg.Arg.Metadata, arg.ClientPrev, arg.Arg.EphemeralLifetime,
  1239  		arg.Arg.CallerPreview)
  1240  	if err != nil {
  1241  		return res, err
  1242  	}
  1243  	_, err = h.G().AttachmentUploader.Register(ctx, uid, arg.Arg.ConversationID, outboxID, arg.Arg.Title,
  1244  		arg.Arg.Filename, nil, arg.Arg.CallerPreview)
  1245  	if err != nil {
  1246  		return res, err
  1247  	}
  1248  	return chat1.PostLocalNonblockRes{
  1249  		OutboxID:         outboxID,
  1250  		IdentifyFailures: identBreaks,
  1251  	}, nil
  1252  }
  1253  
  1254  // PostFileAttachmentLocal implements chat1.LocalInterface.PostFileAttachmentLocal.
  1255  func (h *Server) PostFileAttachmentLocal(ctx context.Context, arg chat1.PostFileAttachmentLocalArg) (res chat1.PostLocalRes, err error) {
  1256  	var identBreaks []keybase1.TLFIdentifyFailure
  1257  	ctx = globals.ChatCtx(ctx, h.G(), arg.Arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1258  	defer h.Trace(ctx, &err, "PostFileAttachmentLocal")()
  1259  	defer h.suspendBgConvLoads(ctx)()
  1260  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1261  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1262  	if err != nil {
  1263  		return res, err
  1264  	}
  1265  
  1266  	// Get base of message we are going to send
  1267  	sender := NewBlockingSender(h.G(), h.boxer, h.remoteClient)
  1268  	_, msgID, err := attachments.NewSender(h.G()).PostFileAttachment(ctx, sender, uid, arg.Arg.ConversationID,
  1269  		arg.Arg.TlfName, arg.Arg.Visibility, arg.Arg.OutboxID, arg.Arg.Filename, arg.Arg.Title,
  1270  		arg.Arg.Metadata, 0, arg.Arg.EphemeralLifetime, arg.Arg.CallerPreview)
  1271  	if err != nil {
  1272  		return res, err
  1273  	}
  1274  	if msgID == nil {
  1275  		return res, errors.New("no message ID returned from post")
  1276  	}
  1277  	return chat1.PostLocalRes{
  1278  		MessageID:        *msgID,
  1279  		IdentifyFailures: identBreaks,
  1280  	}, nil
  1281  }
  1282  
  1283  // DownloadAttachmentLocal implements chat1.LocalInterface.DownloadAttachmentLocal.
  1284  func (h *Server) DownloadAttachmentLocal(ctx context.Context, arg chat1.DownloadAttachmentLocalArg) (res chat1.DownloadAttachmentLocalRes, err error) {
  1285  	var identBreaks []keybase1.TLFIdentifyFailure
  1286  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1287  	defer h.Trace(ctx, &err, "DownloadAttachmentLocal")()
  1288  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1289  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1290  	if err != nil {
  1291  		return res, err
  1292  	}
  1293  	if arg.MessageID == 0 {
  1294  		return res, errors.New("invalid message ID provided")
  1295  	}
  1296  	darg := downloadAttachmentArg{
  1297  		SessionID:        arg.SessionID,
  1298  		ConversationID:   arg.ConversationID,
  1299  		MessageID:        arg.MessageID,
  1300  		Preview:          arg.Preview,
  1301  		IdentifyBehavior: arg.IdentifyBehavior,
  1302  	}
  1303  	cli := h.getStreamUICli()
  1304  	darg.Sink = libkb.NewRemoteStreamBuffered(arg.Sink, cli, arg.SessionID)
  1305  
  1306  	return h.downloadAttachmentLocal(ctx, uid, darg)
  1307  }
  1308  
  1309  func (h *Server) getFileAttachmentDownloadDirs() (cacheDir, downloadDir string) {
  1310  	h.fileAttachmentDownloadConfigurationMu.RLock()
  1311  	defer h.fileAttachmentDownloadConfigurationMu.RUnlock()
  1312  	return h.fileAttachmentDownloadCacheDir, h.fileAttachmentDownloadDownloadDir
  1313  }
  1314  
  1315  func (h *Server) ConfigureFileAttachmentDownloadLocal(ctx context.Context, arg chat1.ConfigureFileAttachmentDownloadLocalArg) (err error) {
  1316  	h.fileAttachmentDownloadConfigurationMu.Lock()
  1317  	defer h.fileAttachmentDownloadConfigurationMu.Unlock()
  1318  	h.fileAttachmentDownloadCacheDir, h.fileAttachmentDownloadDownloadDir = arg.CacheDirOverride, arg.DownloadDirOverride
  1319  	return nil
  1320  }
  1321  
  1322  // DownloadFileAttachmentLocal implements chat1.LocalInterface.DownloadFileAttachmentLocal.
  1323  func (h *Server) DownloadFileAttachmentLocal(ctx context.Context, arg chat1.DownloadFileAttachmentLocalArg) (res chat1.DownloadFileAttachmentLocalRes, err error) {
  1324  	var identBreaks []keybase1.TLFIdentifyFailure
  1325  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1326  	defer h.Trace(ctx, &err, "DownloadFileAttachmentLocal")()
  1327  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1328  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1329  	if err != nil {
  1330  		return res, err
  1331  	}
  1332  	if arg.MessageID == 0 {
  1333  		return res, errors.New("invalid message ID provided")
  1334  	}
  1335  	darg := downloadAttachmentArg{
  1336  		SessionID:        arg.SessionID,
  1337  		ConversationID:   arg.ConversationID,
  1338  		MessageID:        arg.MessageID,
  1339  		Preview:          arg.Preview,
  1340  		IdentifyBehavior: arg.IdentifyBehavior,
  1341  	}
  1342  	cacheDir, downloadDir := h.getFileAttachmentDownloadDirs()
  1343  	downloadParentdir, useArbitraryName := downloadDir, false
  1344  	if arg.DownloadToCache {
  1345  		downloadParentdir, useArbitraryName = cacheDir, true
  1346  	}
  1347  	filePath, sink, err := attachments.SinkFromFilename(ctx, h.G(), uid,
  1348  		arg.ConversationID, arg.MessageID, downloadParentdir, useArbitraryName)
  1349  	if err != nil {
  1350  		return res, err
  1351  	}
  1352  	defer func() {
  1353  		// In the event of any error delete the file if it's empty.
  1354  		if err != nil {
  1355  			h.Debug(ctx, "DownloadFileAttachmentLocal: deleteFileIfEmpty: %v", deleteFileIfEmpty(filePath))
  1356  		}
  1357  	}()
  1358  	if err := attachments.Quarantine(ctx, filePath); err != nil {
  1359  		h.Debug(ctx, "DownloadFileAttachmentLocal: failed to quarantine download: %s", err)
  1360  		return res, err
  1361  	}
  1362  	darg.Sink = sink
  1363  	ires, err := h.downloadAttachmentLocal(ctx, uid, darg)
  1364  	if err != nil {
  1365  		return res, err
  1366  	}
  1367  	return chat1.DownloadFileAttachmentLocalRes{
  1368  		FilePath:         filePath,
  1369  		IdentifyFailures: ires.IdentifyFailures,
  1370  	}, nil
  1371  }
  1372  
  1373  func deleteFileIfEmpty(filename string) (err error) {
  1374  	f, err := os.Stat(filename)
  1375  	if err != nil {
  1376  		return err
  1377  	}
  1378  	if f.Size() == 0 {
  1379  		return os.Remove(filename)
  1380  	}
  1381  	return nil
  1382  }
  1383  
  1384  type downloadAttachmentArg struct {
  1385  	SessionID        int
  1386  	ConversationID   chat1.ConversationID
  1387  	MessageID        chat1.MessageID
  1388  	Sink             io.WriteCloser
  1389  	Preview          bool
  1390  	IdentifyBehavior keybase1.TLFIdentifyBehavior
  1391  }
  1392  
  1393  func (h *Server) downloadAttachmentLocal(ctx context.Context, uid gregor1.UID, arg downloadAttachmentArg) (res chat1.DownloadAttachmentLocalRes, err error) {
  1394  
  1395  	var identBreaks []keybase1.TLFIdentifyFailure
  1396  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1397  	progress := func(bytesComplete, bytesTotal int64) {
  1398  		h.G().NotifyRouter.HandleChatAttachmentDownloadProgress(ctx, keybase1.UID(uid.String()),
  1399  			arg.ConversationID, arg.MessageID, bytesComplete, bytesTotal)
  1400  	}
  1401  
  1402  	h.Debug(ctx, "downloadAttachmentLocal: fetching asset from attachment message: convID: %s messageID: %d",
  1403  		arg.ConversationID, arg.MessageID)
  1404  
  1405  	err = attachments.Download(ctx, h.G(), uid, arg.ConversationID,
  1406  		arg.MessageID, arg.Sink, arg.Preview, progress, h.remoteClient)
  1407  	if err != nil {
  1408  		return res, err
  1409  	}
  1410  	h.G().NotifyRouter.HandleChatAttachmentDownloadComplete(ctx, keybase1.UID(uid.String()),
  1411  		arg.ConversationID, arg.MessageID)
  1412  
  1413  	return chat1.DownloadAttachmentLocalRes{
  1414  		IdentifyFailures: identBreaks,
  1415  	}, nil
  1416  }
  1417  
  1418  func (h *Server) presentUIItem(ctx context.Context, uid gregor1.UID, conv *chat1.ConversationLocal,
  1419  	partMode utils.PresentParticipantsMode) (res *chat1.InboxUIItem) {
  1420  	if conv != nil {
  1421  		pc := utils.PresentConversationLocal(ctx, h.G(), uid, *conv, partMode)
  1422  		res = &pc
  1423  	}
  1424  	return res
  1425  }
  1426  
  1427  func (h *Server) CancelPost(ctx context.Context, outboxID chat1.OutboxID) (err error) {
  1428  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil, h.identNotifier)
  1429  	defer h.Trace(ctx, &err, "CancelPost(%s)", outboxID)()
  1430  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1431  	if err != nil {
  1432  		return err
  1433  	}
  1434  	outbox := storage.NewOutbox(h.G(), uid)
  1435  	obr, err := outbox.RemoveMessage(ctx, outboxID)
  1436  	if err != nil {
  1437  		return err
  1438  	}
  1439  	// Alert the attachment uploader as well, in case this outboxID corresponds to an attachment upload
  1440  	if err := h.G().AttachmentUploader.Cancel(ctx, outboxID); err != nil {
  1441  		return err
  1442  	}
  1443  	convLocal, err := h.G().InboxSource.IncrementLocalConvVersion(ctx, uid, obr.ConvID)
  1444  	if err != nil {
  1445  		h.Debug(ctx, "CancelPost: failed to get IncrementLocalConvVersion")
  1446  	}
  1447  	act := chat1.NewChatActivityWithFailedMessage(chat1.FailedMessageInfo{
  1448  		OutboxRecords: []chat1.OutboxRecord{obr},
  1449  		Conv:          h.presentUIItem(ctx, uid, convLocal, utils.PresentParticipantsModeSkip),
  1450  	})
  1451  	h.G().ActivityNotifier.Activity(context.Background(), uid, chat1.TopicType_NONE, &act,
  1452  		chat1.ChatActivitySource_LOCAL)
  1453  	return h.G().Badger.Send(ctx)
  1454  }
  1455  
  1456  func (h *Server) RetryPost(ctx context.Context, arg chat1.RetryPostArg) (err error) {
  1457  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil, h.identNotifier)
  1458  	defer h.Trace(ctx, &err, fmt.Sprintf("RetryPost: obr: %v", arg.OutboxID))()
  1459  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1460  	if err != nil {
  1461  		return err
  1462  	}
  1463  
  1464  	// Mark as retry in the outbox
  1465  	outbox := storage.NewOutbox(h.G(), uid)
  1466  	obr, err := outbox.RetryMessage(ctx, arg.OutboxID, arg.IdentifyBehavior)
  1467  	if err != nil {
  1468  		return err
  1469  	} else if obr == nil {
  1470  		return nil
  1471  	}
  1472  	if obr.IsAttachment() {
  1473  		if _, err := h.G().AttachmentUploader.Retry(ctx, obr.OutboxID); err != nil {
  1474  			h.Debug(ctx, "RetryPost: failed to retry attachment upload: %s", err)
  1475  		}
  1476  	}
  1477  
  1478  	// Force the send loop to try again
  1479  	h.G().MessageDeliverer.ForceDeliverLoop(ctx)
  1480  
  1481  	return nil
  1482  }
  1483  
  1484  // remoteClient returns a client connection to gregord.
  1485  func (h *Server) remoteClient() chat1.RemoteInterface {
  1486  	if h.rc != nil {
  1487  		return h.rc
  1488  	}
  1489  	return h.serverConn.GetClient()
  1490  }
  1491  
  1492  func (h *Server) setTestRemoteClient(ri chat1.RemoteInterface) {
  1493  	h.rc = ri
  1494  }
  1495  
  1496  func (h *Server) FindGeneralConvFromTeamID(ctx context.Context, teamID keybase1.TeamID) (res chat1.InboxUIItem, err error) {
  1497  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
  1498  	defer h.Trace(ctx, &err, "FindGeneralConvFromTeamID")()
  1499  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1500  	if err != nil {
  1501  		return res, err
  1502  	}
  1503  	tlfID, err := chat1.TeamIDToTLFID(teamID)
  1504  	if err != nil {
  1505  		return res, err
  1506  	}
  1507  	vis := keybase1.TLFVisibility_PRIVATE
  1508  	topicName := globals.DefaultTeamTopic
  1509  	topicType := chat1.TopicType_CHAT
  1510  	query := &chat1.GetInboxLocalQuery{
  1511  		Name: &chat1.NameQuery{
  1512  			TlfID:       &tlfID,
  1513  			MembersType: chat1.ConversationMembersType_TEAM,
  1514  		},
  1515  		TlfVisibility: &vis,
  1516  		TopicName:     &topicName,
  1517  		TopicType:     &topicType,
  1518  	}
  1519  	ib, _, err := h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking,
  1520  		types.InboxSourceDataSourceAll, nil, query)
  1521  	if err != nil {
  1522  		return res, err
  1523  	}
  1524  	if len(ib.Convs) != 1 {
  1525  		return res, libkb.NotFoundError{}
  1526  	}
  1527  	return utils.PresentConversationLocal(ctx, h.G(), uid, ib.Convs[0], utils.PresentParticipantsModeSkip),
  1528  		nil
  1529  }
  1530  
  1531  func (h *Server) FindConversationsLocal(ctx context.Context,
  1532  	arg chat1.FindConversationsLocalArg) (res chat1.FindConversationsLocalRes, err error) {
  1533  	var identBreaks []keybase1.TLFIdentifyFailure
  1534  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  1535  	defer h.Trace(ctx, &err, "FindConversationsLocal")()
  1536  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1537  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1538  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1539  	if err != nil {
  1540  		return res, err
  1541  	}
  1542  
  1543  	res.Conversations, err = FindConversations(ctx, h.G(), h.DebugLabeler,
  1544  		types.InboxSourceDataSourceAll, h.remoteClient,
  1545  		uid, arg.TlfName, arg.TopicType, arg.MembersType, arg.Visibility, arg.TopicName, arg.OneChatPerTLF)
  1546  	if err != nil {
  1547  		return res, err
  1548  	}
  1549  	res.UiConversations = utils.PresentConversationLocals(ctx, h.G(), uid, res.Conversations,
  1550  		utils.PresentParticipantsModeInclude)
  1551  	return res, nil
  1552  }
  1553  
  1554  func (h *Server) UpdateUnsentText(ctx context.Context, arg chat1.UpdateUnsentTextArg) (err error) {
  1555  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  1556  	defer h.Trace(ctx, &err,
  1557  		fmt.Sprintf("UpdateUnsentText convID: %s", arg.ConversationID))()
  1558  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1559  	if err != nil {
  1560  		h.Debug(ctx, "UpdateUnsentText: not logged in: %s", err)
  1561  		return nil
  1562  	}
  1563  
  1564  	// Save draft
  1565  	var draftText *string
  1566  	if len(arg.Text) > 0 {
  1567  		draftText = &arg.Text
  1568  	}
  1569  	if err := h.G().InboxSource.Draft(ctx, uid, arg.ConversationID, draftText); err != nil {
  1570  		h.Debug(ctx, "UpdateUnsentText: failed to save draft: %s", err)
  1571  	}
  1572  
  1573  	// Attempt to prefetch any unfurls in the background that are in the message text
  1574  	go h.G().Unfurler.Prefetch(globals.BackgroundChatCtx(ctx, h.G()), uid, arg.ConversationID, arg.Text)
  1575  
  1576  	// Preview any slash commands in the text
  1577  	go h.G().CommandsSource.PreviewBuiltinCommand(globals.BackgroundChatCtx(ctx, h.G()), uid,
  1578  		arg.ConversationID, arg.TlfName, arg.Text)
  1579  
  1580  	return nil
  1581  }
  1582  
  1583  func (h *Server) UpdateTyping(ctx context.Context, arg chat1.UpdateTypingArg) (err error) {
  1584  	var identBreaks []keybase1.TLFIdentifyFailure
  1585  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1586  		&identBreaks, h.identNotifier)
  1587  	defer h.Trace(ctx, &err,
  1588  		fmt.Sprintf("UpdateTyping convID: %s", arg.ConversationID))()
  1589  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1590  	if err != nil {
  1591  		h.Debug(ctx, "UpdateTyping: not logged in: %s", err)
  1592  		return nil
  1593  	}
  1594  	// Just bail out if we are offline
  1595  	if !h.G().Syncer.IsConnected(ctx) {
  1596  		return nil
  1597  	}
  1598  	deviceID := make([]byte, libkb.DeviceIDLen)
  1599  	if err := h.G().Env.GetDeviceID().ToBytes(deviceID); err != nil {
  1600  		h.Debug(ctx, "UpdateTyping: failed to get device: %s", err)
  1601  		return nil
  1602  	}
  1603  	if err := h.remoteClient().UpdateTypingRemote(ctx, chat1.UpdateTypingRemoteArg{
  1604  		Uid:      uid,
  1605  		DeviceID: deviceID,
  1606  		ConvID:   arg.ConversationID,
  1607  		Typing:   arg.Typing,
  1608  	}); err != nil {
  1609  		h.Debug(ctx, "UpdateTyping: failed to hit the server: %s", err.Error())
  1610  	}
  1611  	return nil
  1612  }
  1613  
  1614  func (h *Server) JoinConversationByIDLocal(ctx context.Context, convID chat1.ConversationID) (res chat1.JoinLeaveConversationLocalRes, err error) {
  1615  	var identBreaks []keybase1.TLFIdentifyFailure
  1616  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1617  		&identBreaks, h.identNotifier)
  1618  	defer h.Trace(ctx, &err, fmt.Sprintf("JoinConversationByIDLocal: convID: %s", convID))()
  1619  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1620  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1621  	defer func() {
  1622  		if res.Offline {
  1623  			h.Debug(ctx, "JoinConversationByIDLocal: result obtained offline")
  1624  		}
  1625  	}()
  1626  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1627  	if err != nil {
  1628  		return res, err
  1629  	}
  1630  	err = JoinConversation(ctx, h.G(), h.DebugLabeler, h.remoteClient, uid, convID)
  1631  	if err != nil {
  1632  		return res, err
  1633  	}
  1634  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1635  	return res, nil
  1636  }
  1637  
  1638  func (h *Server) JoinConversationLocal(ctx context.Context, arg chat1.JoinConversationLocalArg) (res chat1.JoinLeaveConversationLocalRes, err error) {
  1639  	var identBreaks []keybase1.TLFIdentifyFailure
  1640  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1641  		&identBreaks, h.identNotifier)
  1642  	defer h.Trace(ctx, &err, fmt.Sprintf("JoinConversation(%s)",
  1643  		arg.TopicName))()
  1644  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1645  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1646  	defer func() {
  1647  		if res.Offline {
  1648  			h.Debug(ctx, "JoinConversationLocal: result obtained offline")
  1649  		}
  1650  	}()
  1651  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1652  	if err != nil {
  1653  		return res, err
  1654  	}
  1655  	if err = JoinConversationByName(ctx, h.G(), h.DebugLabeler, h.remoteClient, uid, arg.TlfName,
  1656  		arg.TopicName, arg.TopicType, arg.Visibility); err != nil {
  1657  		return res, err
  1658  	}
  1659  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1660  	return res, nil
  1661  }
  1662  
  1663  func (h *Server) LeaveConversationLocal(ctx context.Context, convID chat1.ConversationID) (res chat1.JoinLeaveConversationLocalRes, err error) {
  1664  	var identBreaks []keybase1.TLFIdentifyFailure
  1665  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1666  		&identBreaks, h.identNotifier)
  1667  	defer h.Trace(ctx, &err, fmt.Sprintf("LeaveConversation(%s)", convID))()
  1668  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1669  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1670  	defer func() {
  1671  		if res.Offline {
  1672  			h.Debug(ctx, "LeaveConversationLocal: result obtained offline")
  1673  		}
  1674  	}()
  1675  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1676  	if err != nil {
  1677  		return res, err
  1678  	}
  1679  	err = LeaveConversation(ctx, h.G(), h.DebugLabeler, h.remoteClient, uid, convID)
  1680  	if err != nil {
  1681  		return res, err
  1682  	}
  1683  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1684  	return res, nil
  1685  }
  1686  
  1687  func (h *Server) PreviewConversationByIDLocal(ctx context.Context, convID chat1.ConversationID) (res chat1.PreviewConversationLocalRes, err error) {
  1688  	var identBreaks []keybase1.TLFIdentifyFailure
  1689  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1690  		&identBreaks, h.identNotifier)
  1691  	defer h.Trace(ctx, &err, fmt.Sprintf("PreviewConversationByIDLocal: convID: %s", convID))()
  1692  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1693  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1694  	defer func() {
  1695  		if res.Offline {
  1696  			h.Debug(ctx, "PreviewConversationByIDLocal: result obtained offline")
  1697  		}
  1698  	}()
  1699  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1700  	if err != nil {
  1701  		return res, err
  1702  	}
  1703  	conv, err := PreviewConversation(ctx, h.G(), h.DebugLabeler, h.remoteClient, uid, convID)
  1704  	if err != nil {
  1705  		return res, err
  1706  	}
  1707  	res.Conv = utils.PresentConversationLocal(ctx, h.G(), uid, conv, utils.PresentParticipantsModeInclude)
  1708  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1709  	return res, nil
  1710  }
  1711  
  1712  func (h *Server) DeleteConversationLocal(ctx context.Context, arg chat1.DeleteConversationLocalArg) (res chat1.DeleteConversationLocalRes, err error) {
  1713  	var identBreaks []keybase1.TLFIdentifyFailure
  1714  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1715  		&identBreaks, h.identNotifier)
  1716  	defer h.Trace(ctx, &err, fmt.Sprintf("DeleteConversation(%s)", arg.ConvID))()
  1717  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1718  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1719  	defer func() {
  1720  		if res.Offline {
  1721  			h.Debug(ctx, "DeleteConversationLocal: result obtained offline")
  1722  		}
  1723  	}()
  1724  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1725  	if err != nil {
  1726  		return res, err
  1727  	}
  1728  	ui := h.getChatUI(arg.SessionID)
  1729  	confirmed := arg.Confirmed
  1730  	if !confirmed {
  1731  		confirmed, err = ui.ChatConfirmChannelDelete(ctx, chat1.ChatConfirmChannelDeleteArg{
  1732  			SessionID: arg.SessionID,
  1733  			Channel:   arg.ChannelName,
  1734  		})
  1735  		if err != nil {
  1736  			return res, err
  1737  		}
  1738  	}
  1739  	if !confirmed {
  1740  		return res, errors.New("channel delete unconfirmed")
  1741  	}
  1742  	if err := h.G().InboxSource.RemoteDeleteConversation(ctx, uid, arg.ConvID); err != nil {
  1743  		return res, err
  1744  	}
  1745  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1746  	return res, nil
  1747  }
  1748  
  1749  func (h *Server) RemoveFromConversationLocal(ctx context.Context, arg chat1.RemoveFromConversationLocalArg) (res chat1.RemoveFromConversationLocalRes, err error) {
  1750  	var identBreaks []keybase1.TLFIdentifyFailure
  1751  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1752  		&identBreaks, h.identNotifier)
  1753  	defer h.Trace(ctx, &err, fmt.Sprintf("RemoveFromConversation(%s)", arg.ConvID))()
  1754  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1755  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  1756  		return res, err
  1757  	}
  1758  	err = RemoveFromConversation(ctx, h.G(), h.DebugLabeler, h.remoteClient, arg.ConvID, arg.Usernames)
  1759  	if err != nil {
  1760  		return res, err
  1761  	}
  1762  	return res, nil
  1763  }
  1764  
  1765  func (h *Server) GetTLFConversationsLocal(ctx context.Context, arg chat1.GetTLFConversationsLocalArg) (res chat1.GetTLFConversationsLocalRes, err error) {
  1766  	var identBreaks []keybase1.TLFIdentifyFailure
  1767  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1768  		&identBreaks, h.identNotifier)
  1769  	defer h.Trace(ctx, &err, fmt.Sprintf("GetTLFConversationsLocal(%s)",
  1770  		arg.TlfName))()
  1771  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1772  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1773  	defer func() {
  1774  		if res.Offline {
  1775  			h.Debug(ctx, "GetTLFConversationsLocal: result obtained offline")
  1776  		}
  1777  	}()
  1778  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1779  	if err != nil {
  1780  		return res, err
  1781  	}
  1782  
  1783  	// Fetch the TLF ID from specified name
  1784  	nameInfo, err := CreateNameInfoSource(ctx, h.G(), arg.MembersType).LookupID(ctx, arg.TlfName, false)
  1785  	if err != nil {
  1786  		h.Debug(ctx, "GetTLFConversationsLocal: failed to get TLFID from name: %v", err)
  1787  		return res, err
  1788  	}
  1789  
  1790  	var convs []chat1.ConversationLocal
  1791  	convs, err = h.G().TeamChannelSource.GetChannelsFull(ctx, uid, nameInfo.ID, arg.TopicType)
  1792  	if err != nil {
  1793  		return res, err
  1794  	}
  1795  	res.Convs = utils.PresentConversationLocals(ctx, h.G(), uid, convs, utils.PresentParticipantsModeInclude)
  1796  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1797  	return res, nil
  1798  }
  1799  
  1800  func (h *Server) GetChannelMembershipsLocal(ctx context.Context, arg chat1.GetChannelMembershipsLocalArg) (res chat1.GetChannelMembershipsLocalRes, err error) {
  1801  	var identBreaks []keybase1.TLFIdentifyFailure
  1802  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1803  		&identBreaks, h.identNotifier)
  1804  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1805  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1806  	defer func() {
  1807  		if res.Offline {
  1808  			h.Debug(ctx, "GetTLFConversationsLocal: result obtained offline")
  1809  		}
  1810  	}()
  1811  	myUID, err := utils.AssertLoggedInUID(ctx, h.G())
  1812  	if err != nil {
  1813  		return chat1.GetChannelMembershipsLocalRes{}, err
  1814  	}
  1815  
  1816  	chatTopicType := chat1.TopicType_CHAT
  1817  	tlfID := chat1.TLFID(arg.TeamID.ToBytes())
  1818  
  1819  	// fetch all conversations in the supplied team
  1820  	inbox, err := h.G().InboxSource.ReadUnverified(ctx, myUID, types.InboxSourceDataSourceAll,
  1821  		&chat1.GetInboxQuery{
  1822  			TlfID:        &tlfID,
  1823  			MembersTypes: []chat1.ConversationMembersType{chat1.ConversationMembersType_TEAM},
  1824  			TopicType:    &chatTopicType,
  1825  		})
  1826  	if err != nil {
  1827  		return res, err
  1828  	}
  1829  
  1830  	// find a list of conversations that the provided uid is a member of
  1831  	var memberConvs []types.RemoteConversation
  1832  	for _, conv := range inbox.ConvsUnverified {
  1833  		uids, err := h.G().ParticipantsSource.Get(ctx, myUID, conv.GetConvID(),
  1834  			types.InboxSourceDataSourceAll)
  1835  		if err != nil {
  1836  			return res, err
  1837  		}
  1838  		for _, uid := range uids {
  1839  			if bytes.Equal(uid, arg.Uid) {
  1840  				memberConvs = append(memberConvs, conv)
  1841  				break
  1842  			}
  1843  		}
  1844  	}
  1845  
  1846  	// localize those conversations so we can get the topic name
  1847  	convsLocal, _, err := h.G().InboxSource.Localize(ctx, myUID, memberConvs,
  1848  		types.ConversationLocalizerBlocking)
  1849  	for _, conv := range convsLocal {
  1850  		res.Channels = append(res.Channels, chat1.ChannelNameMention{
  1851  			ConvID:    conv.GetConvID(),
  1852  			TopicName: conv.GetTopicName(),
  1853  		})
  1854  	}
  1855  	return res, nil
  1856  }
  1857  
  1858  func (h *Server) SetAppNotificationSettingsLocal(ctx context.Context,
  1859  	arg chat1.SetAppNotificationSettingsLocalArg) (res chat1.SetAppNotificationSettingsLocalRes, err error) {
  1860  	var identBreaks []keybase1.TLFIdentifyFailure
  1861  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI,
  1862  		&identBreaks, h.identNotifier)
  1863  	defer h.Trace(ctx, &err, fmt.Sprintf("SetAppNotificationSettings(%s)",
  1864  		arg.ConvID))()
  1865  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  1866  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1867  	defer func() {
  1868  		if res.Offline {
  1869  			h.Debug(ctx, "SetAppNotificationSettingsLocal: result obtained offline")
  1870  		}
  1871  	}()
  1872  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  1873  		return res, err
  1874  	}
  1875  
  1876  	var nsettings chat1.ConversationNotificationInfo
  1877  	nsettings.ChannelWide = arg.ChannelWide
  1878  	nsettings.Settings = make(map[keybase1.DeviceType]map[chat1.NotificationKind]bool)
  1879  	nsettings.Settings[keybase1.DeviceType_MOBILE] = make(map[chat1.NotificationKind]bool)
  1880  	nsettings.Settings[keybase1.DeviceType_DESKTOP] = make(map[chat1.NotificationKind]bool)
  1881  	for _, setting := range arg.Settings {
  1882  		nsettings.Settings[setting.DeviceType][setting.Kind] = setting.Enabled
  1883  	}
  1884  	_, err = h.remoteClient().SetAppNotificationSettings(ctx, chat1.SetAppNotificationSettingsArg{
  1885  		ConvID:   arg.ConvID,
  1886  		Settings: nsettings,
  1887  	})
  1888  	if err != nil {
  1889  		h.Debug(ctx, "SetAppNotificationSettings: failed to post to remote: %s", err.Error())
  1890  		return res, err
  1891  	}
  1892  	res.Offline = h.G().InboxSource.IsOffline(ctx)
  1893  	return res, nil
  1894  }
  1895  
  1896  func (h *Server) UnboxMobilePushNotification(ctx context.Context, arg chat1.UnboxMobilePushNotificationArg) (res string, err error) {
  1897  	var identBreaks []keybase1.TLFIdentifyFailure
  1898  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  1899  	defer h.Trace(ctx, &err, fmt.Sprintf("UnboxMobilePushNotification(%s)",
  1900  		arg.ConvID))()
  1901  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1902  	if err != nil {
  1903  		return "", err
  1904  	}
  1905  	bConvID, err := hex.DecodeString(arg.ConvID)
  1906  	if err != nil {
  1907  		h.Debug(ctx, "UnboxMobilePushNotification: invalid convID: %s msg: %s", arg.ConvID, err.Error())
  1908  		return "", err
  1909  	}
  1910  	convID := chat1.ConversationID(bConvID)
  1911  	mp := NewMobilePush(h.G())
  1912  	msg, err := mp.UnboxPushNotification(ctx, uid, convID, arg.MembersType, arg.Payload)
  1913  	if err != nil {
  1914  		return "", err
  1915  	}
  1916  	if _, err := utils.GetVerifiedConv(ctx, h.G(), uid, convID, types.InboxSourceDataSourceAll); err != nil {
  1917  		return "", err
  1918  	}
  1919  	if arg.ShouldAck {
  1920  		mp.AckNotificationSuccess(ctx, arg.PushIDs)
  1921  	}
  1922  
  1923  	if msg.IsValid() {
  1924  		body := msg.Valid().MessageBody
  1925  		bodyTyp, err := body.MessageType()
  1926  		if err != nil {
  1927  			return "", err
  1928  		}
  1929  		if bodyTyp == chat1.MessageType_TEXT {
  1930  			return body.Text().Body, nil
  1931  		}
  1932  	}
  1933  	return res, nil
  1934  }
  1935  
  1936  func (h *Server) SetGlobalAppNotificationSettingsLocal(ctx context.Context,
  1937  	strSettings map[string]bool) (err error) {
  1938  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  1939  	defer h.Trace(ctx, &err, "SetGlobalAppNotificationSettings")()
  1940  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  1941  		return err
  1942  	}
  1943  	return setGlobalAppNotificationSettings(ctx, h.G(), h.remoteClient, strSettings)
  1944  }
  1945  
  1946  func (h *Server) GetGlobalAppNotificationSettingsLocal(ctx context.Context) (res chat1.GlobalAppNotificationSettings, err error) {
  1947  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  1948  	defer h.Trace(ctx, &err, "GetGlobalAppNotificationSettings")()
  1949  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  1950  		return res, err
  1951  	}
  1952  	return getGlobalAppNotificationSettings(ctx, h.G(), h.remoteClient)
  1953  }
  1954  
  1955  func (h *Server) AddTeamMemberAfterReset(ctx context.Context,
  1956  	arg chat1.AddTeamMemberAfterResetArg) (err error) {
  1957  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  1958  	defer h.Trace(ctx, &err, "AddTeamMemberAfterReset")()
  1959  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  1960  	if err != nil {
  1961  		return err
  1962  	}
  1963  
  1964  	// Lookup conversation to get team ID
  1965  	iboxRes, _, err := h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking,
  1966  		types.InboxSourceDataSourceAll, nil,
  1967  		&chat1.GetInboxLocalQuery{
  1968  			ConvIDs: []chat1.ConversationID{arg.ConvID},
  1969  		})
  1970  	if err != nil {
  1971  		return err
  1972  	}
  1973  	if len(iboxRes.Convs) != 1 {
  1974  		return errors.New("failed to find conversation to add reset user back into")
  1975  	}
  1976  	var teamID keybase1.TeamID
  1977  	conv := iboxRes.Convs[0]
  1978  	switch conv.Info.MembersType {
  1979  	case chat1.ConversationMembersType_IMPTEAMUPGRADE:
  1980  		team, err := NewTeamLoader(h.G().ExternalG()).loadTeam(ctx, conv.Info.Triple.Tlfid, conv.Info.TlfName,
  1981  			conv.Info.MembersType, conv.Info.Visibility == keybase1.TLFVisibility_PUBLIC, nil)
  1982  		if err != nil {
  1983  			return err
  1984  		}
  1985  		teamID = team.ID
  1986  	case chat1.ConversationMembersType_IMPTEAMNATIVE, chat1.ConversationMembersType_TEAM:
  1987  		teamID = keybase1.TeamID(conv.Info.Triple.Tlfid.String())
  1988  	default:
  1989  		return fmt.Errorf("unable to add member back to non team conversation: %v",
  1990  			conv.Info.MembersType)
  1991  	}
  1992  	return teams.ReAddMemberAfterReset(ctx, h.G().ExternalG(), teamID, arg.Username)
  1993  }
  1994  
  1995  func (h *Server) GetAllResetConvMembers(ctx context.Context) (res chat1.GetAllResetConvMembersRes, err error) {
  1996  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  1997  	defer h.Trace(ctx, &err, "GetAllResetConvMembers")()
  1998  	defer func() { h.setResultRateLimit(ctx, &res) }()
  1999  	if _, err := utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  2000  		return res, err
  2001  	}
  2002  	resetConvs, err := h.remoteClient().GetResetConversations(ctx)
  2003  	if err != nil {
  2004  		return res, err
  2005  	}
  2006  	for _, resetMember := range resetConvs.ResetConvs {
  2007  		username, err := h.G().GetUPAKLoader().LookupUsername(ctx, keybase1.UID(resetMember.Uid.String()))
  2008  		if err != nil {
  2009  			return res, err
  2010  		}
  2011  		res.Members = append(res.Members, chat1.ResetConvMember{
  2012  			Uid:      resetMember.Uid,
  2013  			Conv:     resetMember.ConvID,
  2014  			Username: username.String(),
  2015  		})
  2016  	}
  2017  	return res, nil
  2018  }
  2019  
  2020  func (h *Server) SetConvRetentionLocal(ctx context.Context, arg chat1.SetConvRetentionLocalArg) (err error) {
  2021  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2022  	defer h.Trace(ctx, &err, "SetConvRetentionLocal(%v, %v)", arg.ConvID,
  2023  		arg.Policy.Summary())()
  2024  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2025  	if err != nil {
  2026  		return err
  2027  	}
  2028  	// short circuit if the policy is unchanged.
  2029  	policy := arg.Policy
  2030  	conv, err := utils.GetVerifiedConv(ctx, h.G(), uid, arg.ConvID, types.InboxSourceDataSourceAll)
  2031  	if err != nil {
  2032  		return err
  2033  	}
  2034  	if convRetention := conv.ConvRetention; convRetention != nil && policy.Eq(*convRetention) {
  2035  		h.Debug(ctx, "retention policy unchanged, skipping update")
  2036  		return nil
  2037  	}
  2038  
  2039  	if _, err = h.remoteClient().SetConvRetention(ctx, chat1.SetConvRetentionArg{
  2040  		ConvID: arg.ConvID,
  2041  		Policy: policy,
  2042  	}); err != nil {
  2043  		return err
  2044  	}
  2045  
  2046  	// Post a SYSTEM message to conversation about the change. If we're
  2047  	// inheriting the team policy, fetch that for the message.
  2048  	isInherit := false
  2049  	typ, err := policy.Typ()
  2050  	if err != nil {
  2051  		return err
  2052  	}
  2053  	switch typ {
  2054  	case chat1.RetentionPolicyType_INHERIT:
  2055  		isInherit = true
  2056  	default:
  2057  		// Nothing to do for other policy types.
  2058  	}
  2059  
  2060  	if isInherit {
  2061  		teamRetention := conv.TeamRetention
  2062  		if teamRetention == nil {
  2063  			policy = chat1.RetentionPolicy{}
  2064  		} else {
  2065  			policy = *teamRetention
  2066  		}
  2067  	}
  2068  	username := h.G().Env.GetUsername()
  2069  	subBody := chat1.NewMessageSystemWithChangeretention(chat1.MessageSystemChangeRetention{
  2070  		User:        username.String(),
  2071  		IsTeam:      false,
  2072  		IsInherit:   isInherit,
  2073  		MembersType: conv.GetMembersType(),
  2074  		Policy:      policy,
  2075  	})
  2076  	body := chat1.NewMessageBodyWithSystem(subBody)
  2077  	return h.G().ChatHelper.SendMsgByID(ctx, arg.ConvID, conv.Info.TlfName, body, chat1.MessageType_SYSTEM,
  2078  		conv.Info.Visibility)
  2079  }
  2080  
  2081  func (h *Server) SetTeamRetentionLocal(ctx context.Context, arg chat1.SetTeamRetentionLocalArg) (err error) {
  2082  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2083  	defer h.Trace(ctx, &err, "SetTeamRetentionLocal(%v, %v)", arg.TeamID, arg.Policy.Summary())()
  2084  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  2085  		return err
  2086  	}
  2087  	teamRetention, err := h.GetTeamRetentionLocal(ctx, arg.TeamID)
  2088  	if err != nil {
  2089  		return err
  2090  	}
  2091  	policy := arg.Policy
  2092  	if teamRetention != nil && policy.Eq(*teamRetention) {
  2093  		h.Debug(ctx, "retention policy unchanged, skipping update")
  2094  		return nil
  2095  	}
  2096  
  2097  	if _, err = h.remoteClient().SetTeamRetention(ctx, chat1.SetTeamRetentionArg{
  2098  		TeamID: arg.TeamID,
  2099  		Policy: policy,
  2100  	}); err != nil {
  2101  		return err
  2102  	}
  2103  
  2104  	// Post a SYSTEM message to the #general channel about the change.
  2105  	tlfID, err := chat1.MakeTLFID(arg.TeamID.String())
  2106  	if err != nil {
  2107  		return err
  2108  	}
  2109  	username := h.G().Env.GetUsername()
  2110  	subBody := chat1.NewMessageSystemWithChangeretention(chat1.MessageSystemChangeRetention{
  2111  		User:        username.String(),
  2112  		IsTeam:      true,
  2113  		IsInherit:   false,
  2114  		MembersType: chat1.ConversationMembersType_TEAM,
  2115  		Policy:      arg.Policy,
  2116  	})
  2117  	body := chat1.NewMessageBodyWithSystem(subBody)
  2118  	info, err := CreateNameInfoSource(ctx, h.G(), chat1.ConversationMembersType_TEAM).LookupName(ctx, tlfID,
  2119  		false, "")
  2120  	if err != nil {
  2121  		return err
  2122  	}
  2123  	return h.G().ChatHelper.SendMsgByName(ctx, info.CanonicalName, &globals.DefaultTeamTopic,
  2124  		chat1.ConversationMembersType_TEAM, keybase1.TLFIdentifyBehavior_CHAT_CLI, body,
  2125  		chat1.MessageType_SYSTEM)
  2126  }
  2127  
  2128  func (h *Server) GetTeamRetentionLocal(ctx context.Context, teamID keybase1.TeamID) (res *chat1.RetentionPolicy, err error) {
  2129  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2130  	defer h.Trace(ctx, &err, "GetTeamRetentionLocal(%s)", teamID)()
  2131  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2132  	if err != nil {
  2133  		return res, err
  2134  	}
  2135  	tlfID, err := chat1.MakeTLFID(teamID.String())
  2136  	if err != nil {
  2137  		return res, err
  2138  	}
  2139  	ib, err := h.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceAll,
  2140  		&chat1.GetInboxQuery{
  2141  			TlfID: &tlfID,
  2142  		})
  2143  	if err != nil {
  2144  		return res, err
  2145  	}
  2146  	if len(ib.ConvsUnverified) == 0 {
  2147  		return res, errors.New("no conversations found")
  2148  	}
  2149  	return ib.ConvsUnverified[0].Conv.TeamRetention, nil
  2150  }
  2151  
  2152  func (h *Server) SetConvMinWriterRoleLocal(ctx context.Context, arg chat1.SetConvMinWriterRoleLocalArg) (err error) {
  2153  	defer h.Trace(ctx, &err, "SetConvMinWriterRole(%v, %v)", arg.ConvID, arg.Role)()
  2154  	_, err = h.remoteClient().SetConvMinWriterRole(ctx, chat1.SetConvMinWriterRoleArg(arg))
  2155  	return err
  2156  }
  2157  
  2158  func (h *Server) UpgradeKBFSConversationToImpteam(ctx context.Context, convID chat1.ConversationID) (err error) {
  2159  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2160  	defer h.Trace(ctx, &err, "UpgradeKBFSConversationToImpteam(%s)", convID)()
  2161  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2162  	if err != nil {
  2163  		return err
  2164  	}
  2165  
  2166  	ibox, _, err := h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking,
  2167  		types.InboxSourceDataSourceAll, nil,
  2168  		&chat1.GetInboxLocalQuery{
  2169  			ConvIDs: []chat1.ConversationID{convID},
  2170  		})
  2171  	if err != nil {
  2172  		return err
  2173  	}
  2174  	if len(ibox.Convs) == 0 {
  2175  		return errors.New("no conversation found")
  2176  	}
  2177  	conv := ibox.Convs[0]
  2178  	if conv.GetMembersType() != chat1.ConversationMembersType_KBFS {
  2179  		return fmt.Errorf("cannot upgrade %v conversation", conv.GetMembersType())
  2180  	}
  2181  	tlfID := conv.Info.Triple.Tlfid
  2182  	tlfName := conv.Info.TlfName
  2183  	public := conv.Info.Visibility == keybase1.TLFVisibility_PUBLIC
  2184  	return h.G().ChatHelper.UpgradeKBFSToImpteam(ctx, tlfName, tlfID, public)
  2185  }
  2186  
  2187  func (h *Server) cancelActiveSearchLocked() {
  2188  	if h.searchCancelFn != nil {
  2189  		h.searchCancelFn()
  2190  		h.searchCancelFn = nil
  2191  	}
  2192  }
  2193  
  2194  func (h *Server) getSearchContext(ctx context.Context) context.Context {
  2195  	// enforce a single search happening at a time
  2196  	h.searchMu.Lock()
  2197  	h.cancelActiveSearchLocked()
  2198  	ctx, h.searchCancelFn = context.WithCancel(ctx)
  2199  	h.searchMu.Unlock()
  2200  	return ctx
  2201  }
  2202  
  2203  func (h *Server) CancelActiveSearch(ctx context.Context) (err error) {
  2204  	defer h.Trace(ctx, &err, "CancelActiveSearch")()
  2205  	h.searchMu.Lock()
  2206  	h.cancelActiveSearchLocked()
  2207  	h.searchMu.Unlock()
  2208  	return nil
  2209  }
  2210  
  2211  func (h *Server) getSearchRegexp(query string, opts chat1.SearchOpts) (re *regexp.Regexp, err error) {
  2212  	if opts.IsRegex {
  2213  		re, err = regexp.Compile(query)
  2214  	} else {
  2215  		// String queries are set case insensitive
  2216  		re, err = utils.GetQueryRe(query)
  2217  	}
  2218  	if err != nil {
  2219  		return nil, err
  2220  	}
  2221  	return re, nil
  2222  }
  2223  
  2224  func (h *Server) SearchRegexp(ctx context.Context, arg chat1.SearchRegexpArg) (res chat1.SearchRegexpRes, err error) {
  2225  	var identBreaks []keybase1.TLFIdentifyFailure
  2226  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  2227  	defer h.Trace(ctx, &err, "SearchRegexp")()
  2228  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  2229  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2230  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2231  	if err != nil {
  2232  		return res, err
  2233  	}
  2234  	ctx = h.getSearchContext(ctx)
  2235  
  2236  	query, opts := search.UpgradeSearchOptsFromQuery(arg.Query, arg.Opts,
  2237  		h.G().GetEnv().GetUsername().String())
  2238  	re, err := h.getSearchRegexp(query, opts)
  2239  	if err != nil {
  2240  		return res, err
  2241  	}
  2242  
  2243  	chatUI := h.getChatUI(arg.SessionID)
  2244  	uiCh := make(chan chat1.ChatSearchHit, 10)
  2245  	ch := make(chan struct{})
  2246  	go func() {
  2247  		for searchHit := range uiCh {
  2248  			select {
  2249  			case <-ctx.Done():
  2250  				return
  2251  			default:
  2252  				_ = chatUI.ChatSearchHit(ctx, chat1.ChatSearchHitArg{
  2253  					SearchHit: searchHit,
  2254  				})
  2255  			}
  2256  		}
  2257  		close(ch)
  2258  	}()
  2259  	hits, _, err := h.G().RegexpSearcher.Search(ctx, uid, arg.ConvID, re, uiCh, opts)
  2260  	if err != nil {
  2261  		return res, err
  2262  	}
  2263  
  2264  	<-ch
  2265  	_ = chatUI.ChatSearchDone(ctx, chat1.ChatSearchDoneArg{
  2266  		NumHits: len(hits),
  2267  	})
  2268  	return chat1.SearchRegexpRes{
  2269  		Hits:             hits,
  2270  		IdentifyFailures: identBreaks,
  2271  	}, nil
  2272  }
  2273  
  2274  func (h *Server) cancelActiveInboxSearchLocked() {
  2275  	if h.searchInboxCancelFn != nil {
  2276  		h.searchInboxCancelFn()
  2277  		h.searchInboxCancelFn = nil
  2278  	}
  2279  }
  2280  
  2281  func (h *Server) getInboxSearchContext(ctx context.Context) context.Context {
  2282  	// enforce a single search happening at a time
  2283  	h.searchInboxMu.Lock()
  2284  	h.cancelActiveInboxSearchLocked()
  2285  	ctx, h.searchInboxCancelFn = context.WithCancel(ctx)
  2286  	h.searchInboxMu.Unlock()
  2287  	return ctx
  2288  }
  2289  
  2290  func (h *Server) CancelActiveInboxSearch(ctx context.Context) (err error) {
  2291  	defer h.Trace(ctx, &err, "CancelActiveInboxSearch")()
  2292  	h.searchInboxMu.Lock()
  2293  	h.cancelActiveInboxSearchLocked()
  2294  	h.searchInboxMu.Unlock()
  2295  	return nil
  2296  }
  2297  
  2298  func (h *Server) delegateInboxSearch(ctx context.Context, uid gregor1.UID, query, origQuery string,
  2299  	opts chat1.SearchOpts, ui libkb.ChatUI) (res chat1.ChatSearchInboxResults, err error) {
  2300  	defer h.Trace(ctx, &err, "delegateInboxSearch")()
  2301  	convs, err := h.G().Indexer.SearchableConvs(ctx, opts.ConvID)
  2302  	if err != nil {
  2303  		return res, err
  2304  	}
  2305  	re, err := h.getSearchRegexp(query, opts)
  2306  	if err != nil {
  2307  		return res, err
  2308  	}
  2309  	select {
  2310  	case <-ctx.Done():
  2311  		return res, ctx.Err()
  2312  	default:
  2313  		_ = ui.ChatSearchConvHits(ctx, chat1.UIChatSearchConvHits{})
  2314  	}
  2315  	var numHits, numConvs int
  2316  	for index, conv := range convs {
  2317  		uiCh := make(chan chat1.ChatSearchHit, 10)
  2318  		ch := make(chan struct{})
  2319  		go func() {
  2320  			for searchHit := range uiCh {
  2321  				select {
  2322  				case <-ctx.Done():
  2323  					return
  2324  				default:
  2325  					_ = ui.ChatSearchHit(ctx, chat1.ChatSearchHitArg{
  2326  						SearchHit: searchHit,
  2327  					})
  2328  				}
  2329  			}
  2330  			close(ch)
  2331  		}()
  2332  		hits, _, err := h.G().RegexpSearcher.Search(ctx, uid, conv.GetConvID(), re, uiCh, opts)
  2333  		if err != nil {
  2334  			h.Debug(ctx, "delegateInboxSearch: failed to search conv: %s", err)
  2335  			continue
  2336  		}
  2337  		<-ch
  2338  		if len(hits) == 0 {
  2339  			continue
  2340  		}
  2341  		numHits += len(hits)
  2342  		numConvs++
  2343  		inboxHit := chat1.ChatSearchInboxHit{
  2344  			ConvID:   conv.GetConvID(),
  2345  			TeamType: conv.GetTeamType(),
  2346  			ConvName: utils.GetRemoteConvDisplayName(conv),
  2347  			Query:    origQuery,
  2348  			Time:     hits[0].HitMessage.Valid().Ctime,
  2349  			Hits:     hits,
  2350  		}
  2351  		select {
  2352  		case <-ctx.Done():
  2353  			return res, ctx.Err()
  2354  		default:
  2355  			_ = ui.ChatSearchInboxHit(ctx, chat1.ChatSearchInboxHitArg{
  2356  				SearchHit: inboxHit,
  2357  			})
  2358  		}
  2359  		res.Hits = append(res.Hits, inboxHit)
  2360  		if opts.MaxConvsHit > 0 && len(res.Hits) > opts.MaxConvsHit {
  2361  			break
  2362  		}
  2363  		if opts.MaxConvsSearched > 0 && index > opts.MaxConvsSearched {
  2364  			break
  2365  		}
  2366  	}
  2367  
  2368  	select {
  2369  	case <-ctx.Done():
  2370  		return res, ctx.Err()
  2371  	default:
  2372  		_ = ui.ChatSearchInboxDone(ctx, chat1.ChatSearchInboxDoneArg{
  2373  			Res: chat1.ChatSearchInboxDone{
  2374  				NumHits:   numHits,
  2375  				NumConvs:  numConvs,
  2376  				Delegated: true,
  2377  			},
  2378  		})
  2379  	}
  2380  	return res, nil
  2381  }
  2382  
  2383  func (h *Server) SearchInbox(ctx context.Context, arg chat1.SearchInboxArg) (res chat1.SearchInboxRes, err error) {
  2384  	var identBreaks []keybase1.TLFIdentifyFailure
  2385  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  2386  	defer h.Trace(ctx, &err, "SearchInbox")()
  2387  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  2388  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2389  	defer h.suspendBgConvLoads(ctx)()
  2390  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2391  	if err != nil {
  2392  		return res, err
  2393  	}
  2394  
  2395  	chatUI := h.getChatUI(arg.SessionID)
  2396  	select {
  2397  	case <-ctx.Done():
  2398  		return res, ctx.Err()
  2399  	default:
  2400  		_ = chatUI.ChatSearchInboxStart(ctx)
  2401  	}
  2402  
  2403  	username := h.G().GetEnv().GetUsernameForUID(keybase1.UID(uid.String())).String()
  2404  	query, opts := search.UpgradeSearchOptsFromQuery(arg.Query, arg.Opts, username)
  2405  	doSearch := !arg.NamesOnly && len(query) > 0
  2406  	forceDelegate := false
  2407  	if arg.Opts.ConvID != nil {
  2408  		fullyIndexed, err := h.G().Indexer.FullyIndexed(ctx, *arg.Opts.ConvID)
  2409  		if err != nil {
  2410  			h.Debug(ctx, "SearchInbox: failed to check fully indexed, delegating... err: %s", err)
  2411  			forceDelegate = true
  2412  		} else {
  2413  			forceDelegate = !fullyIndexed
  2414  		}
  2415  		if len(query) < search.MinTokenLength {
  2416  			forceDelegate = true
  2417  		}
  2418  		if forceDelegate {
  2419  			h.Debug(ctx, "SearchInbox: force delegating since not indexed")
  2420  		}
  2421  		ctx = h.getSearchContext(ctx)
  2422  	} else {
  2423  		ctx = h.getInboxSearchContext(ctx)
  2424  	}
  2425  
  2426  	if doSearch && (opts.IsRegex || forceDelegate) {
  2427  		inboxRes, err := h.delegateInboxSearch(ctx, uid, query, arg.Query, opts, chatUI)
  2428  		if err != nil {
  2429  			return res, err
  2430  		}
  2431  		res.Res = &inboxRes
  2432  		res.IdentifyFailures = identBreaks
  2433  		return res, nil
  2434  	}
  2435  	eg := errgroup.Group{}
  2436  
  2437  	// stream hits back to client UI
  2438  	hitUICh := make(chan chat1.ChatSearchInboxHit, 10)
  2439  	var numHits int
  2440  	eg.Go(func() error {
  2441  		if !doSearch {
  2442  			return nil
  2443  		}
  2444  		for searchHit := range hitUICh {
  2445  			numHits += len(searchHit.Hits)
  2446  			select {
  2447  			case <-ctx.Done():
  2448  				return nil
  2449  			default:
  2450  				_ = chatUI.ChatSearchInboxHit(ctx, chat1.ChatSearchInboxHitArg{
  2451  					SearchHit: searchHit,
  2452  				})
  2453  			}
  2454  		}
  2455  		return nil
  2456  	})
  2457  
  2458  	// stream index status back to client UI
  2459  	indexUICh := make(chan chat1.ChatSearchIndexStatus, 10)
  2460  	eg.Go(func() error {
  2461  		if !doSearch {
  2462  			return nil
  2463  		}
  2464  		for status := range indexUICh {
  2465  			select {
  2466  			case <-ctx.Done():
  2467  				return nil
  2468  			default:
  2469  				_ = chatUI.ChatSearchIndexStatus(ctx, chat1.ChatSearchIndexStatusArg{
  2470  					Status: status,
  2471  				})
  2472  			}
  2473  		}
  2474  		return nil
  2475  	})
  2476  
  2477  	// send up conversation name matches
  2478  	eg.Go(func() error {
  2479  		if opts.MaxNameConvs == 0 {
  2480  			return nil
  2481  		}
  2482  		convHits, err := h.G().InboxSource.Search(ctx, uid, query, opts.MaxNameConvs,
  2483  			types.InboxSourceSearchEmptyModeUnread)
  2484  		if err != nil {
  2485  			h.Debug(ctx, "SearchInbox: failed to get conv hits: %s", err)
  2486  			return nil
  2487  		}
  2488  		select {
  2489  		case <-ctx.Done():
  2490  			return ctx.Err()
  2491  		default:
  2492  			_ = chatUI.ChatSearchConvHits(ctx, chat1.UIChatSearchConvHits{
  2493  				Hits:          utils.PresentRemoteConversationsAsSearchHits(convHits, username),
  2494  				UnreadMatches: len(query) == 0,
  2495  			})
  2496  		}
  2497  		return nil
  2498  	})
  2499  
  2500  	// send up team name matches
  2501  	g := h.G().ExternalG()
  2502  	mctx := libkb.NewMetaContext(ctx, g)
  2503  	eg.Go(func() error {
  2504  		if opts.MaxTeams == 0 {
  2505  			return nil
  2506  		}
  2507  		hits, err := opensearch.Local(mctx, query,
  2508  			opts.MaxTeams)
  2509  		if err != nil {
  2510  			h.Debug(ctx, "SearchInbox: failed to get team hits: %s", err)
  2511  			return nil
  2512  		}
  2513  		select {
  2514  		case <-ctx.Done():
  2515  			return nil
  2516  		default:
  2517  			_ = chatUI.ChatSearchTeamHits(ctx, chat1.UIChatSearchTeamHits{
  2518  				Hits:             hits,
  2519  				SuggestedMatches: len(query) == 0,
  2520  			})
  2521  		}
  2522  		return nil
  2523  	})
  2524  
  2525  	eg.Go(func() error {
  2526  		hits, err := teambot.NewFeaturedBotLoader(g).SearchLocal(
  2527  			mctx, keybase1.SearchLocalArg{
  2528  				Query:     query,
  2529  				Limit:     opts.MaxBots,
  2530  				SkipCache: opts.SkipBotCache,
  2531  			})
  2532  		if err != nil {
  2533  			h.Debug(ctx, "SearchInbox: failed to get bot hits: %s", err)
  2534  			return nil
  2535  		}
  2536  		select {
  2537  		case <-ctx.Done():
  2538  			return nil
  2539  		default:
  2540  			_ = chatUI.ChatSearchBotHits(ctx, chat1.UIChatSearchBotHits{
  2541  				Hits:             hits.Bots,
  2542  				SuggestedMatches: len(query) == 0,
  2543  			})
  2544  		}
  2545  		return nil
  2546  	})
  2547  
  2548  	var searchRes *chat1.ChatSearchInboxResults
  2549  	if doSearch {
  2550  		select {
  2551  		case <-time.After(50 * time.Millisecond):
  2552  		case <-ctx.Done():
  2553  			return
  2554  		}
  2555  		if searchRes, err = h.G().Indexer.Search(ctx, query, arg.Query, opts, hitUICh, indexUICh); err != nil {
  2556  			return res, err
  2557  		}
  2558  	}
  2559  
  2560  	if err := eg.Wait(); err != nil {
  2561  		h.Debug(ctx, "unable to wait for search: %v")
  2562  	}
  2563  
  2564  	var doneRes chat1.ChatSearchInboxDone
  2565  	if searchRes != nil {
  2566  		doneRes = chat1.ChatSearchInboxDone{
  2567  			NumHits:        numHits,
  2568  			NumConvs:       len(searchRes.Hits),
  2569  			PercentIndexed: searchRes.PercentIndexed,
  2570  		}
  2571  	}
  2572  	select {
  2573  	case <-ctx.Done():
  2574  		return res, ctx.Err()
  2575  	default:
  2576  		_ = chatUI.ChatSearchInboxDone(ctx, chat1.ChatSearchInboxDoneArg{
  2577  			Res: doneRes,
  2578  		})
  2579  	}
  2580  	return chat1.SearchInboxRes{
  2581  		Res:              searchRes,
  2582  		IdentifyFailures: identBreaks,
  2583  	}, nil
  2584  }
  2585  
  2586  func (h *Server) ProfileChatSearch(ctx context.Context, identifyBehavior keybase1.TLFIdentifyBehavior) (
  2587  	res map[chat1.ConvIDStr]chat1.ProfileSearchConvStats, err error) {
  2588  	var identBreaks []keybase1.TLFIdentifyFailure
  2589  	ctx = globals.ChatCtx(ctx, h.G(), identifyBehavior, &identBreaks, h.identNotifier)
  2590  	defer h.Trace(ctx, &err, "ProfileChatSearch")()
  2591  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  2592  	if err != nil {
  2593  		return nil, err
  2594  	}
  2595  
  2596  	res, err = h.G().Indexer.IndexInbox(ctx)
  2597  	if err != nil {
  2598  		return nil, err
  2599  	}
  2600  	b, err := json.Marshal(res)
  2601  	if err != nil {
  2602  		return nil, err
  2603  	}
  2604  	h.Debug(ctx, "%s\n", string(b))
  2605  	return res, err
  2606  }
  2607  
  2608  func (h *Server) GetStaticConfig(ctx context.Context) (res chat1.StaticConfig, err error) {
  2609  	defer h.Trace(ctx, &err, "GetStaticConfig")()
  2610  	return chat1.StaticConfig{
  2611  		DeletableByDeleteHistory: chat1.DeletableMessageTypesByDeleteHistory(),
  2612  		BuiltinCommands:          h.G().CommandsSource.GetBuiltins(ctx),
  2613  	}, nil
  2614  }
  2615  
  2616  func (h *Server) ResolveUnfurlPrompt(ctx context.Context, arg chat1.ResolveUnfurlPromptArg) (err error) {
  2617  	var identBreaks []keybase1.TLFIdentifyFailure
  2618  	defer func() {
  2619  		// squash all errors coming out of here
  2620  		err = nil
  2621  	}()
  2622  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  2623  	defer h.Trace(ctx, &err, "ResolveUnfurlPrompt")()
  2624  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2625  	if err != nil {
  2626  		return err
  2627  	}
  2628  	fetchAndUnfurl := func() error {
  2629  		msgs, err := h.G().ConvSource.GetMessages(ctx, arg.ConvID, uid, []chat1.MessageID{arg.MsgID},
  2630  			nil, nil, true)
  2631  		if err != nil {
  2632  			return err
  2633  		}
  2634  		if len(msgs) != 1 {
  2635  			return errors.New("message not found")
  2636  		}
  2637  		h.G().Unfurler.UnfurlAndSend(ctx, uid, arg.ConvID, msgs[0])
  2638  		return nil
  2639  	}
  2640  	atyp, err := arg.Result.ActionType()
  2641  	if err != nil {
  2642  		return err
  2643  	}
  2644  	switch atyp {
  2645  	case chat1.UnfurlPromptAction_NOTNOW:
  2646  		// do nothing
  2647  	case chat1.UnfurlPromptAction_ACCEPT:
  2648  		if err = h.G().Unfurler.WhitelistAdd(ctx, uid, arg.Result.Accept()); err != nil {
  2649  			return fmt.Errorf("failed to add to whitelist, doing nothing: %s", err)
  2650  		}
  2651  		if err = fetchAndUnfurl(); err != nil {
  2652  			return fmt.Errorf("failed to fetch and unfurl: %s", err)
  2653  		}
  2654  	case chat1.UnfurlPromptAction_ONETIME:
  2655  		h.G().Unfurler.WhitelistAddExemption(ctx, uid,
  2656  			unfurl.NewSingleMessageWhitelistExemption(arg.ConvID, arg.MsgID, arg.Result.Onetime()))
  2657  		if err = fetchAndUnfurl(); err != nil {
  2658  			return fmt.Errorf("failed to fetch and unfurl: %s", err)
  2659  		}
  2660  	case chat1.UnfurlPromptAction_NEVER:
  2661  		if err = h.G().Unfurler.SetMode(ctx, uid, chat1.UnfurlMode_NEVER); err != nil {
  2662  			return fmt.Errorf("failed to set mode to never: %s", err)
  2663  		}
  2664  	case chat1.UnfurlPromptAction_ALWAYS:
  2665  		if err = h.G().Unfurler.SetMode(ctx, uid, chat1.UnfurlMode_ALWAYS); err != nil {
  2666  			return fmt.Errorf("failed to set mode to always: %s", err)
  2667  		}
  2668  		if err = fetchAndUnfurl(); err != nil {
  2669  			return fmt.Errorf("failed to fetch and unfurl: %s", err)
  2670  		}
  2671  	}
  2672  	return nil
  2673  }
  2674  
  2675  func (h *Server) GetUnfurlSettings(ctx context.Context) (res chat1.UnfurlSettingsDisplay, err error) {
  2676  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2677  	defer h.Trace(ctx, &err, "GetUnfurlSettings")()
  2678  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2679  	if err != nil {
  2680  		return res, err
  2681  	}
  2682  	settings, err := h.G().Unfurler.GetSettings(ctx, uid)
  2683  	if err != nil {
  2684  		return res, err
  2685  	}
  2686  	res.Mode = settings.Mode
  2687  	for w := range settings.Whitelist {
  2688  		res.Whitelist = append(res.Whitelist, w)
  2689  	}
  2690  	sort.Slice(res.Whitelist, func(i, j int) bool {
  2691  		return res.Whitelist[i] < res.Whitelist[j]
  2692  	})
  2693  	return res, nil
  2694  }
  2695  
  2696  func (h *Server) SaveUnfurlSettings(ctx context.Context, arg chat1.SaveUnfurlSettingsArg) (err error) {
  2697  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2698  	defer h.Trace(ctx, &err, "SaveUnfurlSettings")()
  2699  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2700  	if err != nil {
  2701  		return err
  2702  	}
  2703  	wm := make(map[string]bool)
  2704  	for _, w := range arg.Whitelist {
  2705  		wm[w] = true
  2706  	}
  2707  	return h.G().Unfurler.SetSettings(ctx, uid, chat1.UnfurlSettings{
  2708  		Mode:      arg.Mode,
  2709  		Whitelist: wm,
  2710  	})
  2711  }
  2712  
  2713  func (h *Server) ToggleMessageCollapse(ctx context.Context, arg chat1.ToggleMessageCollapseArg) (err error) {
  2714  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2715  	defer h.Trace(ctx, &err, "ToggleMessageCollapse convID=%s msgID=%d collapsed=%v",
  2716  		arg.ConvID, arg.MsgID, arg.Collapse)()
  2717  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2718  	if err != nil {
  2719  		return err
  2720  	}
  2721  	if err := utils.NewCollapses(h.G()).ToggleSingle(ctx, uid, arg.ConvID, arg.MsgID, arg.Collapse); err != nil {
  2722  		return err
  2723  	}
  2724  	msg, err := h.G().ConvSource.GetMessage(ctx, arg.ConvID, uid, arg.MsgID, nil, nil, true)
  2725  	if err != nil {
  2726  		h.Debug(ctx, "ToggleMessageCollapse: failed to get message: %s", err)
  2727  		return nil
  2728  	}
  2729  	if !msg.IsValid() {
  2730  		h.Debug(ctx, "ToggleMessageCollapse: invalid message")
  2731  		return nil
  2732  	}
  2733  	if msg.Valid().MessageBody.IsType(chat1.MessageType_UNFURL) {
  2734  		unfurledMsg, err := h.G().ConvSource.GetMessage(ctx, arg.ConvID, uid,
  2735  			msg.Valid().MessageBody.Unfurl().MessageID, nil, nil, true)
  2736  		if err != nil {
  2737  			h.Debug(ctx, "ToggleMessageCollapse: failed to get unfurl base message: %s", err)
  2738  			return nil
  2739  		}
  2740  		// give a notification about the unfurled message
  2741  		notif := chat1.MessagesUpdated{
  2742  			ConvID:  arg.ConvID,
  2743  			Updates: []chat1.UIMessage{utils.PresentMessageUnboxed(ctx, h.G(), unfurledMsg, uid, arg.ConvID)},
  2744  		}
  2745  		act := chat1.NewChatActivityWithMessagesUpdated(notif)
  2746  		h.G().ActivityNotifier.Activity(ctx, uid, chat1.TopicType_CHAT,
  2747  			&act, chat1.ChatActivitySource_LOCAL)
  2748  	} else if msg.Valid().MessageBody.IsType(chat1.MessageType_ATTACHMENT) {
  2749  		notif := chat1.MessagesUpdated{
  2750  			ConvID:  arg.ConvID,
  2751  			Updates: []chat1.UIMessage{utils.PresentMessageUnboxed(ctx, h.G(), msg, uid, arg.ConvID)},
  2752  		}
  2753  		act := chat1.NewChatActivityWithMessagesUpdated(notif)
  2754  		h.G().ActivityNotifier.Activity(ctx, uid, chat1.TopicType_CHAT,
  2755  			&act, chat1.ChatActivitySource_LOCAL)
  2756  	}
  2757  	return nil
  2758  }
  2759  
  2760  func (h *Server) BulkAddToConv(ctx context.Context, arg chat1.BulkAddToConvArg) (err error) {
  2761  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2762  	defer h.Trace(ctx, &err, "BulkAddToConv: convID: %v, numUsers: %v", arg.ConvID, len(arg.Usernames))()
  2763  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2764  	if err != nil {
  2765  		return err
  2766  	}
  2767  	return h.G().ChatHelper.BulkAddToConv(ctx, uid, arg.ConvID, arg.Usernames)
  2768  }
  2769  
  2770  func (h *Server) BulkAddToManyConvs(ctx context.Context, arg chat1.BulkAddToManyConvsArg) (err error) {
  2771  	for _, convID := range arg.Conversations {
  2772  		if err = h.BulkAddToConv(ctx, chat1.BulkAddToConvArg{
  2773  			ConvID:    convID,
  2774  			Usernames: arg.Usernames,
  2775  		}); err != nil {
  2776  			return err
  2777  		}
  2778  	}
  2779  	return nil
  2780  }
  2781  
  2782  func (h *Server) PutReacjiSkinTone(ctx context.Context, skinTone keybase1.ReacjiSkinTone) (res keybase1.UserReacjis, err error) {
  2783  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2784  	defer h.Trace(ctx, &err, "PutReacjiSkinTone")()
  2785  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2786  	if err != nil {
  2787  		return res, err
  2788  	}
  2789  	store := storage.NewReacjiStore(h.G())
  2790  	err = store.PutSkinTone(ctx, uid, skinTone)
  2791  	if err != nil {
  2792  		return res, err
  2793  	}
  2794  	res = store.UserReacjis(ctx, uid)
  2795  	return res, nil
  2796  }
  2797  
  2798  func (h *Server) ResolveMaybeMention(ctx context.Context, mention chat1.MaybeMention) (err error) {
  2799  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2800  	defer h.Trace(ctx, &err, "ResolveMaybeMention")()
  2801  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2802  	if err != nil {
  2803  		return err
  2804  	}
  2805  
  2806  	// Try to load as user
  2807  	if mention.Channel == "" {
  2808  		nn := libkb.NewNormalizedUsername(mention.Name)
  2809  		if _, err = h.G().GetUPAKLoader().LookupUID(ctx, nn); err != nil {
  2810  			h.Debug(ctx, "ResolveMaybeMention: not a user")
  2811  		} else {
  2812  			_ = h.getChatUI(0).ChatMaybeMentionUpdate(ctx, mention.Name, mention.Channel,
  2813  				chat1.NewUIMaybeMentionInfoWithUser())
  2814  			return nil
  2815  		}
  2816  	}
  2817  	// Try to load as team
  2818  	return h.G().TeamMentionLoader.LoadTeamMention(ctx, uid, mention, nil, true)
  2819  }
  2820  
  2821  func (h *Server) getLoadGalleryContext(ctx context.Context) context.Context {
  2822  	// enforce a single search happening at a time
  2823  	h.loadGalleryMu.Lock()
  2824  	if h.loadGalleryCancelFn != nil {
  2825  		h.loadGalleryCancelFn()
  2826  		h.loadGalleryCancelFn = nil
  2827  	}
  2828  	ctx, h.loadGalleryCancelFn = context.WithCancel(ctx)
  2829  	h.loadGalleryMu.Unlock()
  2830  	return ctx
  2831  }
  2832  
  2833  func (h *Server) LoadGallery(ctx context.Context, arg chat1.LoadGalleryArg) (res chat1.LoadGalleryRes, err error) {
  2834  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  2835  	defer h.Trace(ctx, &err, "LoadGallery")()
  2836  	defer func() { err = h.squashSquashableErrors(err) }()
  2837  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2838  	defer h.suspendBgConvLoads(ctx)()
  2839  
  2840  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2841  	if err != nil {
  2842  		return res, err
  2843  	}
  2844  	ctx = h.getLoadGalleryContext(ctx)
  2845  	chatUI := h.getChatUI(arg.SessionID)
  2846  	convID := arg.ConvID
  2847  	var opts attachments.NextMessageOptions
  2848  	opts.BackInTime = true
  2849  	switch arg.Typ {
  2850  	case chat1.GalleryItemTyp_MEDIA:
  2851  		opts.MessageType = chat1.MessageType_ATTACHMENT
  2852  		opts.AssetTypes = []chat1.AssetMetadataType{chat1.AssetMetadataType_IMAGE,
  2853  			chat1.AssetMetadataType_VIDEO}
  2854  	case chat1.GalleryItemTyp_LINK:
  2855  		opts.MessageType = chat1.MessageType_TEXT
  2856  		opts.FilterLinks = true
  2857  	case chat1.GalleryItemTyp_DOC:
  2858  		opts.MessageType = chat1.MessageType_ATTACHMENT
  2859  		opts.AssetTypes = []chat1.AssetMetadataType{chat1.AssetMetadataType_NONE}
  2860  	default:
  2861  		return res, errors.New("invalid gallery type")
  2862  	}
  2863  	var msgID chat1.MessageID
  2864  	if arg.FromMsgID != nil {
  2865  		msgID = *arg.FromMsgID
  2866  	} else {
  2867  		conv, err := utils.GetUnverifiedConv(ctx, h.G(), uid, convID, types.InboxSourceDataSourceAll)
  2868  		if err != nil {
  2869  			return res, err
  2870  		}
  2871  		msgID = conv.Conv.ReaderInfo.MaxMsgid + 1
  2872  	}
  2873  
  2874  	hitCh := make(chan chat1.UIMessage)
  2875  	go func(ctx context.Context) {
  2876  		for msg := range hitCh {
  2877  			_ = chatUI.ChatLoadGalleryHit(ctx, msg)
  2878  		}
  2879  	}(ctx)
  2880  	gallery := attachments.NewGallery(h.G())
  2881  	msgs, last, err := gallery.NextMessages(ctx, uid, convID, msgID, arg.Num, opts, hitCh)
  2882  	if err != nil {
  2883  		return res, err
  2884  	}
  2885  	return chat1.LoadGalleryRes{
  2886  		Last:     last,
  2887  		Messages: utils.PresentMessagesUnboxed(ctx, h.G(), msgs, uid, convID),
  2888  	}, nil
  2889  }
  2890  
  2891  func (h *Server) LoadFlip(ctx context.Context, arg chat1.LoadFlipArg) (res chat1.LoadFlipRes, err error) {
  2892  	var identBreaks []keybase1.TLFIdentifyFailure
  2893  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  2894  	defer h.Trace(ctx, &err, "LoadFlip")()
  2895  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2896  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  2897  	if err != nil {
  2898  		return res, err
  2899  	}
  2900  	statusCh, errCh := h.G().CoinFlipManager.LoadFlip(ctx, uid, arg.HostConvID, arg.HostMsgID,
  2901  		arg.FlipConvID, arg.GameID)
  2902  	select {
  2903  	case status := <-statusCh:
  2904  		res.Status = status
  2905  	case err = <-errCh:
  2906  		return res, err
  2907  	}
  2908  	res.IdentifyFailures = identBreaks
  2909  	return res, nil
  2910  }
  2911  
  2912  func (h *Server) LocationUpdate(ctx context.Context, coord chat1.Coordinate) (err error) {
  2913  	var identBreaks []keybase1.TLFIdentifyFailure
  2914  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  2915  	defer h.Trace(ctx, &err, "LocationUpdate")()
  2916  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  2917  	if err != nil {
  2918  		return err
  2919  	}
  2920  	h.G().LiveLocationTracker.LocationUpdate(ctx, coord)
  2921  	return nil
  2922  }
  2923  
  2924  func (h *Server) AdvertiseBotCommandsLocal(ctx context.Context, arg chat1.AdvertiseBotCommandsLocalArg) (res chat1.AdvertiseBotCommandsLocalRes, err error) {
  2925  	var identBreaks []keybase1.TLFIdentifyFailure
  2926  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  2927  	defer h.Trace(ctx, &err, "AdvertiseBotCommandsLocal")()
  2928  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2929  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  2930  	if err != nil {
  2931  		return res, err
  2932  	}
  2933  	if err := h.G().BotCommandManager.Advertise(ctx, arg.Alias, arg.Advertisements); err != nil {
  2934  		return res, err
  2935  	}
  2936  	return res, nil
  2937  }
  2938  
  2939  func (h *Server) ClearBotCommandsLocal(ctx context.Context, filter *chat1.ClearBotCommandsFilter) (res chat1.ClearBotCommandsLocalRes, err error) {
  2940  	var identBreaks []keybase1.TLFIdentifyFailure
  2941  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  2942  	defer h.Trace(ctx, &err, "ClearBotCommandsLocal")()
  2943  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2944  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  2945  	if err != nil {
  2946  		return res, err
  2947  	}
  2948  	if err := h.G().BotCommandManager.Clear(ctx, filter); err != nil {
  2949  		return res, err
  2950  	}
  2951  	return res, nil
  2952  }
  2953  
  2954  func (h *Server) ListPublicBotCommandsLocal(ctx context.Context, username string) (res chat1.ListBotCommandsLocalRes, err error) {
  2955  	var identBreaks []keybase1.TLFIdentifyFailure
  2956  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  2957  	defer h.Trace(ctx, &err, "ListPublicBotCommandsLocal")()
  2958  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2959  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  2960  	if err != nil {
  2961  		return res, err
  2962  	}
  2963  
  2964  	convID, err := h.G().BotCommandManager.PublicCommandsConv(ctx, username)
  2965  	if err != nil {
  2966  		if _, ok := err.(UnknownTLFNameError); ok {
  2967  			h.Debug(ctx, "ListPublicBotCommandsLocal: unknown conv name")
  2968  			return res, nil
  2969  		}
  2970  		return res, err
  2971  	}
  2972  	if convID == nil {
  2973  		return res, nil
  2974  	}
  2975  	return h.ListBotCommandsLocal(ctx, *convID)
  2976  }
  2977  
  2978  func (h *Server) ListBotCommandsLocal(ctx context.Context, convID chat1.ConversationID) (res chat1.ListBotCommandsLocalRes, err error) {
  2979  	var identBreaks []keybase1.TLFIdentifyFailure
  2980  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  2981  	defer h.Trace(ctx, &err, "ListBotCommandsLocal")()
  2982  	defer func() { h.setResultRateLimit(ctx, &res) }()
  2983  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  2984  	if err != nil {
  2985  		return res, err
  2986  	}
  2987  	completeCh, err := h.G().BotCommandManager.UpdateCommands(ctx, convID, nil)
  2988  	if err != nil {
  2989  		return res, err
  2990  	}
  2991  	if err := <-completeCh; err != nil {
  2992  		h.Debug(ctx, "ListBotCommandsLocal: failed to update commands, list might be stale: %s", err)
  2993  	}
  2994  	lres, _, err := h.G().BotCommandManager.ListCommands(ctx, convID)
  2995  	if err != nil {
  2996  		return res, err
  2997  	}
  2998  	res.Commands = lres
  2999  	return res, nil
  3000  }
  3001  
  3002  func (h *Server) GetMutualTeamsLocal(ctx context.Context, usernames []string) (res chat1.GetMutualTeamsLocalRes, err error) {
  3003  	var identBreaks []keybase1.TLFIdentifyFailure
  3004  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3005  	defer h.Trace(ctx, &err, "GetMutualTeamsLocal")()
  3006  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3007  	defer func() { err = h.handleOfflineError(ctx, err, &res) }()
  3008  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3009  	if err != nil {
  3010  		return res, err
  3011  	}
  3012  
  3013  	providedUIDs := make([]keybase1.UID, 0, len(usernames))
  3014  	for _, username := range usernames {
  3015  		providedUIDs = append(providedUIDs, libkb.GetUIDByUsername(h.G().GlobalContext, username))
  3016  	}
  3017  
  3018  	// get all the default channels for all the teams you're in
  3019  	chatTopic := chat1.TopicType_CHAT
  3020  	inbox, err := h.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceLocalOnly,
  3021  		&chat1.GetInboxQuery{
  3022  			MembersTypes:     []chat1.ConversationMembersType{chat1.ConversationMembersType_TEAM},
  3023  			TopicName:        &globals.DefaultTeamTopic,
  3024  			TopicType:        &chatTopic,
  3025  			AllowUnseenQuery: true,
  3026  		})
  3027  	if err != nil {
  3028  		return res, err
  3029  	}
  3030  
  3031  	// loop through convs
  3032  	var resLock sync.Mutex
  3033  	pipeliner := pipeliner.NewPipeliner(4)
  3034  	for _, conv := range inbox.ConvsUnverified {
  3035  		if conv.GetMembersType() != chat1.ConversationMembersType_TEAM {
  3036  			continue
  3037  		}
  3038  		if err := pipeliner.WaitForRoom(ctx); err != nil {
  3039  			return res, err
  3040  		}
  3041  		go func(conv types.RemoteConversation) {
  3042  			var err error
  3043  			userPresent := make(map[keybase1.UID]bool)
  3044  			for _, uid := range providedUIDs {
  3045  				userPresent[keybase1.UID(uid.String())] = false
  3046  			}
  3047  			members, err := h.G().ParticipantsSource.Get(ctx, uid, conv.GetConvID(),
  3048  				types.InboxSourceDataSourceAll)
  3049  			if err != nil {
  3050  				pipeliner.CompleteOne(err)
  3051  				return
  3052  			}
  3053  			for _, uid := range members {
  3054  				if _, exists := userPresent[keybase1.UID(uid.String())]; exists {
  3055  					// if we see a user in a team that we're looking for, mark that in the userPresent map
  3056  					userPresent[keybase1.UID(uid.String())] = true
  3057  				}
  3058  			}
  3059  			allOK := true
  3060  			for _, inTeam := range userPresent {
  3061  				if !inTeam {
  3062  					allOK = false
  3063  					break
  3064  				}
  3065  			}
  3066  			if allOK {
  3067  				resLock.Lock()
  3068  				res.TeamIDs = append(res.TeamIDs,
  3069  					keybase1.TeamID(conv.Conv.Metadata.IdTriple.Tlfid.String()))
  3070  				resLock.Unlock()
  3071  			}
  3072  			pipeliner.CompleteOne(nil)
  3073  		}(conv)
  3074  	}
  3075  	err = pipeliner.Flush(ctx)
  3076  	return res, err
  3077  }
  3078  
  3079  func (h *Server) PinMessage(ctx context.Context, arg chat1.PinMessageArg) (res chat1.PinMessageRes, err error) {
  3080  	var identBreaks []keybase1.TLFIdentifyFailure
  3081  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3082  	defer h.Trace(ctx, &err, "PinMessage")()
  3083  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3084  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3085  	if err != nil {
  3086  		return res, err
  3087  	}
  3088  	msg, err := h.G().ConvSource.GetMessage(ctx, arg.ConvID, uid, arg.MsgID, nil, nil, true)
  3089  	if err != nil {
  3090  		return res, err
  3091  	}
  3092  	if !msg.IsValid() {
  3093  		return res, fmt.Errorf("unable to pin message, message invalid")
  3094  	}
  3095  	bod := msg.Valid().MessageBody
  3096  	typ, err := bod.MessageType()
  3097  	if err != nil {
  3098  		return res, err
  3099  	}
  3100  	switch typ {
  3101  	case chat1.MessageType_TEXT,
  3102  		chat1.MessageType_ATTACHMENT:
  3103  	default:
  3104  		return res, fmt.Errorf("Unable to pin messageof type %v, expected %v or %v",
  3105  			typ, chat1.MessageType_TEXT, chat1.MessageType_ATTACHMENT)
  3106  	}
  3107  	if _, err := h.PostLocalNonblock(ctx, chat1.PostLocalNonblockArg{
  3108  		ConversationID: arg.ConvID,
  3109  		Msg: chat1.MessagePlaintext{
  3110  			ClientHeader: chat1.MessageClientHeader{
  3111  				MessageType: chat1.MessageType_PIN,
  3112  			},
  3113  			MessageBody: chat1.NewMessageBodyWithPin(chat1.MessagePin{
  3114  				MsgID: arg.MsgID,
  3115  			}),
  3116  		},
  3117  	}); err != nil {
  3118  		return res, err
  3119  	}
  3120  	return res, nil
  3121  }
  3122  
  3123  func (h *Server) UnpinMessage(ctx context.Context, convID chat1.ConversationID) (res chat1.PinMessageRes, err error) {
  3124  	var identBreaks []keybase1.TLFIdentifyFailure
  3125  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3126  	defer h.Trace(ctx, &err, "UnpinMessage")()
  3127  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3128  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3129  	if err != nil {
  3130  		return res, err
  3131  	}
  3132  	conv, err := utils.GetVerifiedConv(ctx, h.G(), uid, convID, types.InboxSourceDataSourceAll)
  3133  	if err != nil {
  3134  		return res, err
  3135  	}
  3136  	pin, err := conv.GetMaxMessage(chat1.MessageType_PIN)
  3137  	if err != nil {
  3138  		return res, err
  3139  	}
  3140  	if _, err := h.PostLocal(ctx, chat1.PostLocalArg{
  3141  		ConversationID: convID,
  3142  		Msg: chat1.MessagePlaintext{
  3143  			ClientHeader: chat1.MessageClientHeader{
  3144  				MessageType: chat1.MessageType_DELETE,
  3145  				Supersedes:  pin.GetMessageID(),
  3146  			},
  3147  		},
  3148  	}); err != nil {
  3149  		return res, err
  3150  	}
  3151  	return res, nil
  3152  }
  3153  
  3154  func (h *Server) IgnorePinnedMessage(ctx context.Context, convID chat1.ConversationID) (err error) {
  3155  	var identBreaks []keybase1.TLFIdentifyFailure
  3156  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3157  	defer h.Trace(ctx, &err, "IgnorePinnedMessage")()
  3158  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3159  	if err != nil {
  3160  		return err
  3161  	}
  3162  	conv, err := utils.GetVerifiedConv(ctx, h.G(), uid, convID, types.InboxSourceDataSourceAll)
  3163  	if err != nil {
  3164  		return err
  3165  	}
  3166  	pin, err := conv.GetMaxMessage(chat1.MessageType_PIN)
  3167  	if err != nil {
  3168  		return err
  3169  	}
  3170  	if err := storage.NewPinIgnore(h.G(), uid).Ignore(ctx, convID, pin.GetMessageID()); err != nil {
  3171  		return err
  3172  	}
  3173  	h.G().InboxSource.NotifyUpdate(ctx, uid, convID)
  3174  	return nil
  3175  }
  3176  
  3177  func (h *Server) validateBotRole(ctx context.Context, role keybase1.TeamRole) error {
  3178  	switch role {
  3179  	case keybase1.TeamRole_BOT,
  3180  		keybase1.TeamRole_RESTRICTEDBOT:
  3181  		return nil
  3182  	default:
  3183  		return fmt.Errorf("Only %v and %v are valid roles. Found %v",
  3184  			keybase1.TeamRole_BOT, keybase1.TeamRole_RESTRICTEDBOT, role)
  3185  	}
  3186  }
  3187  
  3188  func (h *Server) teamIDFromTLFName(ctx context.Context, membersType chat1.ConversationMembersType,
  3189  	tlfName string, isPublic bool) (res keybase1.TeamID, err error) {
  3190  
  3191  	switch membersType {
  3192  	case chat1.ConversationMembersType_KBFS:
  3193  		return res, errors.New("unable to find a team for KBFS conv")
  3194  	case chat1.ConversationMembersType_TEAM, chat1.ConversationMembersType_IMPTEAMNATIVE,
  3195  		chat1.ConversationMembersType_IMPTEAMUPGRADE:
  3196  		nameInfo, err := CreateNameInfoSource(ctx, h.G(), membersType).LookupID(ctx, tlfName, isPublic)
  3197  		if err != nil {
  3198  			return "", err
  3199  		}
  3200  		if membersType == chat1.ConversationMembersType_IMPTEAMUPGRADE {
  3201  			team, err := NewTeamLoader(h.G().ExternalG()).loadTeam(ctx, nameInfo.ID, tlfName,
  3202  				membersType, isPublic, nil)
  3203  			if err != nil {
  3204  				return res, err
  3205  			}
  3206  			return team.ID, nil
  3207  		}
  3208  		return keybase1.TeamIDFromString(nameInfo.ID.String())
  3209  	}
  3210  	return res, fmt.Errorf("unknown members type: %v", membersType)
  3211  }
  3212  
  3213  func (h *Server) fixupTeamErrorWithTLFName(ctx context.Context, username, tlfName string, err error) error {
  3214  	switch err.(type) {
  3215  	case nil:
  3216  		return nil
  3217  	case libkb.ExistsError:
  3218  		h.Debug(ctx, "fixupTeamErrorWithTLFName: %v", err)
  3219  		return libkb.ExistsError{Msg: fmt.Sprintf(
  3220  			"user %q is already a member of team %q", username, tlfName)}
  3221  	case libkb.NotFoundError:
  3222  		h.Debug(ctx, "fixupTeamErrorWithTLFName: %v", err)
  3223  		return libkb.NotFoundError{Msg: fmt.Sprintf(
  3224  			"user %q is not a member of team %q", username, tlfName)}
  3225  	default:
  3226  		return err
  3227  	}
  3228  }
  3229  
  3230  func (h *Server) teamIDFromConvID(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (id keybase1.TeamID, conv chat1.ConversationLocal, err error) {
  3231  	if conv, err = utils.GetVerifiedConv(ctx, h.G(), uid, convID, types.InboxSourceDataSourceAll); err != nil {
  3232  		return id, conv, err
  3233  	}
  3234  	team, err := NewTeamLoader(h.G().ExternalG()).loadTeam(ctx, conv.Info.Triple.Tlfid,
  3235  		conv.Info.TlfName, conv.GetMembersType(), conv.IsPublic(), nil)
  3236  	if err != nil {
  3237  		return id, conv, err
  3238  	}
  3239  	return team.ID, conv, nil
  3240  }
  3241  
  3242  func (h *Server) AddBotMember(ctx context.Context, arg chat1.AddBotMemberArg) (err error) {
  3243  	var identBreaks []keybase1.TLFIdentifyFailure
  3244  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3245  	defer h.Trace(ctx, &err, "AddBotMember")()
  3246  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3247  	if err != nil {
  3248  		return err
  3249  	}
  3250  
  3251  	if err := h.validateBotRole(ctx, arg.Role); err != nil {
  3252  		return err
  3253  	}
  3254  	teamID, conv, err := h.teamIDFromConvID(ctx, uid, arg.ConvID)
  3255  	if err != nil {
  3256  		return err
  3257  	}
  3258  	defer func() { err = h.fixupTeamErrorWithTLFName(ctx, arg.Username, conv.Info.TlfName, err) }()
  3259  	_, err = teams.AddMemberByID(ctx, h.G().ExternalG(), teamID, arg.Username, arg.Role, arg.BotSettings, nil /* emailInviteMsg */)
  3260  	if err != nil {
  3261  		return err
  3262  	}
  3263  	err = teams.SendTeamChatWelcomeMessage(ctx, h.G().ExternalG(), teamID, conv.Info.TlfName,
  3264  		arg.Username, conv.GetMembersType(), arg.Role)
  3265  	if err != nil {
  3266  		return err
  3267  	}
  3268  	return nil
  3269  }
  3270  
  3271  func (h *Server) EditBotMember(ctx context.Context, arg chat1.EditBotMemberArg) (err error) {
  3272  	var identBreaks []keybase1.TLFIdentifyFailure
  3273  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3274  	defer h.Trace(ctx, &err, "EditBotMember")()
  3275  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3276  	if err != nil {
  3277  		return err
  3278  	}
  3279  	if err := h.validateBotRole(ctx, arg.Role); err != nil {
  3280  		return err
  3281  	}
  3282  	teamID, _, err := h.teamIDFromConvID(ctx, uid, arg.ConvID)
  3283  	if err != nil {
  3284  		return err
  3285  	}
  3286  	return teams.EditMemberByID(ctx, h.G().ExternalG(), teamID, arg.Username, arg.Role, arg.BotSettings)
  3287  }
  3288  
  3289  func (h *Server) RemoveBotMember(ctx context.Context, arg chat1.RemoveBotMemberArg) (err error) {
  3290  	var identBreaks []keybase1.TLFIdentifyFailure
  3291  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3292  	defer h.Trace(ctx, &err, "RemoveBotMember")()
  3293  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3294  	if err != nil {
  3295  		return err
  3296  	}
  3297  	teamID, conv, err := h.teamIDFromConvID(ctx, uid, arg.ConvID)
  3298  	if err != nil {
  3299  		return err
  3300  	}
  3301  	defer func() { err = h.fixupTeamErrorWithTLFName(ctx, arg.Username, conv.Info.TlfName, err) }()
  3302  	return teams.RemoveMemberByID(ctx, h.G().ExternalG(), teamID, arg.Username)
  3303  }
  3304  
  3305  func (h *Server) SetBotMemberSettings(ctx context.Context, arg chat1.SetBotMemberSettingsArg) (err error) {
  3306  	var identBreaks []keybase1.TLFIdentifyFailure
  3307  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3308  	defer h.Trace(ctx, &err, "SetBotMemberSettings")()
  3309  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3310  	if err != nil {
  3311  		return err
  3312  	}
  3313  	teamID, conv, err := h.teamIDFromConvID(ctx, uid, arg.ConvID)
  3314  	if err != nil {
  3315  		return err
  3316  	}
  3317  	defer func() { err = h.fixupTeamErrorWithTLFName(ctx, arg.Username, conv.Info.TlfName, err) }()
  3318  	return teams.SetBotSettingsByID(ctx, h.G().ExternalG(), teamID, arg.Username, arg.BotSettings)
  3319  }
  3320  
  3321  func (h *Server) GetBotMemberSettings(ctx context.Context, arg chat1.GetBotMemberSettingsArg) (res keybase1.TeamBotSettings, err error) {
  3322  	var identBreaks []keybase1.TLFIdentifyFailure
  3323  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3324  	defer h.Trace(ctx, &err, "GetBotMemberSettings")()
  3325  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3326  	if err != nil {
  3327  		return res, err
  3328  	}
  3329  	teamID, conv, err := h.teamIDFromConvID(ctx, uid, arg.ConvID)
  3330  	if err != nil {
  3331  		return res, err
  3332  	}
  3333  	defer func() { err = h.fixupTeamErrorWithTLFName(ctx, arg.Username, conv.Info.TlfName, err) }()
  3334  	return teams.GetBotSettingsByID(ctx, h.G().ExternalG(), teamID, arg.Username)
  3335  }
  3336  
  3337  func (h *Server) GetTeamRoleInConversation(ctx context.Context, arg chat1.GetTeamRoleInConversationArg) (res keybase1.TeamRole, err error) {
  3338  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
  3339  	defer h.Trace(ctx, &err, "GetTeamRoleInConversation")()
  3340  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3341  	if err != nil {
  3342  		return res, err
  3343  	}
  3344  	teamID, _, err := h.teamIDFromConvID(ctx, uid, arg.ConvID)
  3345  	if err != nil {
  3346  		return res, err
  3347  	}
  3348  	return teams.MemberRoleFromID(ctx, h.G().ExternalG(), teamID, arg.Username)
  3349  }
  3350  
  3351  func (h *Server) SimpleSearchInboxConvNames(ctx context.Context, query string) (res []chat1.SimpleSearchInboxConvNamesHit, err error) {
  3352  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
  3353  	defer h.Trace(ctx, &err, "SimpleSearchInboxConvNames")()
  3354  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3355  	if err != nil {
  3356  		return res, err
  3357  	}
  3358  	username := h.G().GetEnv().GetUsername().String()
  3359  	allConvs, err := h.G().InboxSource.Search(ctx, uid, query,
  3360  		100, types.InboxSourceSearchEmptyModeAllBySendCtime)
  3361  	if err != nil {
  3362  		return res, err
  3363  	}
  3364  	for _, conv := range allConvs {
  3365  		switch conv.GetTeamType() {
  3366  		case chat1.TeamType_NONE:
  3367  			searchable := utils.SearchableRemoteConversationName(conv, username)
  3368  			res = append(res, chat1.SimpleSearchInboxConvNamesHit{
  3369  				Name:    searchable,
  3370  				ConvID:  conv.GetConvID(),
  3371  				Parts:   strings.Split(searchable, ","),
  3372  				TlfName: utils.GetRemoteConvTLFName(conv),
  3373  			})
  3374  		case chat1.TeamType_SIMPLE, chat1.TeamType_COMPLEX:
  3375  			res = append(res, chat1.SimpleSearchInboxConvNamesHit{
  3376  				Name:    utils.SearchableRemoteConversationName(conv, username),
  3377  				ConvID:  conv.GetConvID(),
  3378  				IsTeam:  true,
  3379  				TlfName: utils.GetRemoteConvTLFName(conv),
  3380  			})
  3381  		}
  3382  	}
  3383  	return res, nil
  3384  }
  3385  
  3386  func (h *Server) AddBotConvSearch(ctx context.Context, term string) (res []chat1.ConvSearchHit, err error) {
  3387  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
  3388  	defer h.Trace(ctx, &err, "AddBotConvSearch")()
  3389  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3390  	if err != nil {
  3391  		return res, err
  3392  	}
  3393  	username := h.G().GetEnv().GetUsername().String()
  3394  	allConvs, err := h.G().InboxSource.Search(ctx, uid, term, 100, types.InboxSourceSearchEmptyModeAll)
  3395  	if err != nil {
  3396  		return res, err
  3397  	}
  3398  	res = make([]chat1.ConvSearchHit, 0, len(allConvs))
  3399  	for _, conv := range allConvs {
  3400  		switch conv.GetTeamType() {
  3401  		case chat1.TeamType_NONE:
  3402  			searchable := utils.SearchableRemoteConversationName(conv, username)
  3403  			res = append(res, chat1.ConvSearchHit{
  3404  				Name:   searchable,
  3405  				ConvID: conv.GetConvID(),
  3406  				Parts:  strings.Split(searchable, ","),
  3407  			})
  3408  		case chat1.TeamType_SIMPLE:
  3409  			res = append(res, chat1.ConvSearchHit{
  3410  				Name:   utils.SearchableRemoteConversationName(conv, username),
  3411  				ConvID: conv.GetConvID(),
  3412  				IsTeam: true,
  3413  			})
  3414  		case chat1.TeamType_COMPLEX:
  3415  			if conv.Conv.Metadata.IsDefaultConv {
  3416  				res = append(res, chat1.ConvSearchHit{
  3417  					Name:   utils.GetRemoteConvTLFName(conv),
  3418  					ConvID: conv.GetConvID(),
  3419  					IsTeam: true,
  3420  				})
  3421  			}
  3422  		}
  3423  	}
  3424  	return res, nil
  3425  }
  3426  
  3427  func (h *Server) TeamIDFromTLFName(ctx context.Context, arg chat1.TeamIDFromTLFNameArg) (res keybase1.TeamID, err error) {
  3428  	var identBreaks []keybase1.TLFIdentifyFailure
  3429  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3430  	defer h.Trace(ctx, &err, "TeamIDFromTLFName")()
  3431  	if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
  3432  		return res, err
  3433  	}
  3434  
  3435  	return h.teamIDFromTLFName(ctx, arg.MembersType, arg.TlfName, arg.TlfPublic)
  3436  }
  3437  
  3438  func (h *Server) DismissJourneycard(ctx context.Context, arg chat1.DismissJourneycardArg) (err error) {
  3439  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3440  	defer h.Trace(ctx, &err, "DismissJourneycard")()
  3441  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3442  	if err != nil {
  3443  		return err
  3444  	}
  3445  	inbox, err := h.G().InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceLocalOnly,
  3446  		&chat1.GetInboxQuery{
  3447  			ConvID:           &arg.ConvID,
  3448  			SkipBgLoads:      true,
  3449  			AllowUnseenQuery: true,
  3450  		})
  3451  	if err != nil {
  3452  		return err
  3453  	}
  3454  	switch len(inbox.ConvsUnverified) {
  3455  	case 0:
  3456  		return fmt.Errorf("could not find conversation")
  3457  	case 1:
  3458  		if inbox.ConvsUnverified[0].LocalMetadata == nil {
  3459  			return fmt.Errorf("no local metadata")
  3460  		}
  3461  		teamID, err := keybase1.TeamIDFromString(inbox.ConvsUnverified[0].Conv.Metadata.IdTriple.Tlfid.String())
  3462  		if err != nil {
  3463  			return err
  3464  		}
  3465  		h.G().JourneyCardManager.Dismiss(ctx, uid, teamID, arg.ConvID, arg.CardType)
  3466  		return nil
  3467  	default:
  3468  		return fmt.Errorf("got %v conversations but expected 1", len(inbox.ConvsUnverified))
  3469  	}
  3470  }
  3471  
  3472  const welcomeMessageName = "__welcome_message"
  3473  
  3474  // welcomeMessageMaxLen is duplicated at
  3475  // shared/teams/edit-welcome-message/index.tsx:welcomeMessageMaxLen; keep the
  3476  // values in sync!
  3477  const welcomeMessageMaxLen = 400
  3478  
  3479  func getWelcomeMessageConv(ctx context.Context, g *globals.Context, uid gregor1.UID, teamID keybase1.TeamID) (res types.RemoteConversation, err error) {
  3480  	onePerTlf := true
  3481  	tlfID := chat1.TLFID(teamID.ToBytes())
  3482  	topicType := chat1.TopicType_CHAT
  3483  	inbox, err := g.InboxSource.ReadUnverified(ctx, uid, types.InboxSourceDataSourceLocalOnly,
  3484  		&chat1.GetInboxQuery{
  3485  			MembersTypes:      []chat1.ConversationMembersType{chat1.ConversationMembersType_TEAM},
  3486  			TlfID:             &tlfID,
  3487  			TopicType:         &topicType,
  3488  			AllowUnseenQuery:  true,
  3489  			OneChatTypePerTLF: &onePerTlf,
  3490  		})
  3491  	if err != nil {
  3492  		return res, err
  3493  	}
  3494  	if len(inbox.ConvsUnverified) == 0 {
  3495  		return res, libkb.NotFoundError{}
  3496  	}
  3497  	return inbox.ConvsUnverified[0], nil
  3498  }
  3499  
  3500  func getWelcomeMessage(ctx context.Context, g *globals.Context, ri func() chat1.RemoteInterface, uid gregor1.UID, teamID keybase1.TeamID) (message chat1.WelcomeMessageDisplay, err error) {
  3501  	conv, err := getWelcomeMessageConv(ctx, g, uid, teamID)
  3502  	if err != nil {
  3503  		return message, err
  3504  	}
  3505  	message = chat1.WelcomeMessageDisplay{Set: false}
  3506  	s := NewConvDevConversationBackedStorage(g, chat1.TopicType_DEV, true /* adminOnly */, ri)
  3507  	found, _, err := s.Get(ctx, uid, conv.GetConvID(), welcomeMessageName, &message, false)
  3508  	if !found {
  3509  		return message, nil
  3510  	}
  3511  	switch err.(type) {
  3512  	case nil:
  3513  	case *DevStorageAdminOnlyError:
  3514  		return message, nil
  3515  	default:
  3516  		return message, err
  3517  	}
  3518  	if len(message.Raw) > welcomeMessageMaxLen {
  3519  		return message, nil
  3520  	}
  3521  	message.Display = utils.PresentDecoratedTextNoMentions(ctx, message.Raw)
  3522  	return message, nil
  3523  }
  3524  
  3525  func setWelcomeMessage(ctx context.Context, g *globals.Context, ri func() chat1.RemoteInterface, uid gregor1.UID, teamID keybase1.TeamID, message chat1.WelcomeMessage) (err error) {
  3526  	if len(message.Raw) > welcomeMessageMaxLen {
  3527  		return fmt.Errorf("welcome message must be at most %d characters; was %d", welcomeMessageMaxLen, len(message.Raw))
  3528  	}
  3529  	conv, err := getWelcomeMessageConv(ctx, g, uid, teamID)
  3530  	if err != nil {
  3531  		return err
  3532  	}
  3533  	s := NewConvDevConversationBackedStorage(g, chat1.TopicType_DEV, true /* adminOnly */, ri)
  3534  	return s.Put(ctx, uid, conv.GetConvID(), welcomeMessageName, message)
  3535  }
  3536  
  3537  func (h *Server) SetWelcomeMessage(ctx context.Context, arg chat1.SetWelcomeMessageArg) (err error) {
  3538  	var identBreaks []keybase1.TLFIdentifyFailure
  3539  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3540  	defer h.Trace(ctx, &err, "SetWelcomeMessage")()
  3541  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3542  	if err != nil {
  3543  		return err
  3544  	}
  3545  	return setWelcomeMessage(ctx, h.G(), h.remoteClient, uid, arg.TeamID, arg.Message)
  3546  }
  3547  
  3548  func (h *Server) GetWelcomeMessage(ctx context.Context, teamID keybase1.TeamID) (res chat1.WelcomeMessageDisplay, err error) {
  3549  	var identBreaks []keybase1.TLFIdentifyFailure
  3550  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
  3551  	defer h.Trace(ctx, &err, "GetWelcomeMessage")()
  3552  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3553  	if err != nil {
  3554  		return res, err
  3555  	}
  3556  	return getWelcomeMessage(ctx, h.G(), h.remoteClient, uid, teamID)
  3557  }
  3558  
  3559  func (h *Server) GetDefaultTeamChannelsLocal(ctx context.Context, teamID keybase1.TeamID) (res chat1.GetDefaultTeamChannelsLocalRes, err error) {
  3560  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3561  	defer h.Trace(ctx, &err, fmt.Sprintf("GetDefaultTeamChannelsLocal %v", teamID))()
  3562  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3563  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3564  	if err != nil {
  3565  		return res, err
  3566  	}
  3567  
  3568  	resp, err := h.remoteClient().GetDefaultTeamChannels(ctx, teamID)
  3569  	if err != nil {
  3570  		return res, err
  3571  	}
  3572  	if len(resp.Convs) == 0 {
  3573  		return res, nil
  3574  	}
  3575  	topicType := chat1.TopicType_CHAT
  3576  	query := &chat1.GetInboxLocalQuery{
  3577  		ConvIDs:   resp.Convs,
  3578  		TopicType: &topicType,
  3579  	}
  3580  	ib, _, err := h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking,
  3581  		types.InboxSourceDataSourceAll, nil, query)
  3582  	if err != nil {
  3583  		return res, err
  3584  	}
  3585  	res.Convs = utils.PresentConversationLocals(ctx, h.G(), uid, ib.Convs,
  3586  		utils.PresentParticipantsModeSkip)
  3587  	return res, nil
  3588  }
  3589  
  3590  func (h *Server) SetDefaultTeamChannelsLocal(ctx context.Context, arg chat1.SetDefaultTeamChannelsLocalArg) (res chat1.SetDefaultTeamChannelsLocalRes, err error) {
  3591  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3592  	defer h.Trace(ctx, &err, fmt.Sprintf("SetDefaultTeamChannelsLocal: %v", arg.TeamID))()
  3593  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3594  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  3595  	if err != nil {
  3596  		return res, err
  3597  	}
  3598  
  3599  	convs := make([]chat1.ConversationID, 0, len(arg.Convs))
  3600  	for _, conv := range arg.Convs {
  3601  		convID, err := chat1.MakeConvID(conv.String())
  3602  		if err != nil {
  3603  			return res, err
  3604  		}
  3605  		convs = append(convs, convID)
  3606  	}
  3607  	_, err = h.remoteClient().SetDefaultTeamChannels(ctx, chat1.SetDefaultTeamChannelsArg{
  3608  		TeamID: arg.TeamID,
  3609  		Convs:  convs,
  3610  	})
  3611  	return res, err
  3612  }
  3613  
  3614  func (h *Server) GetLastActiveForTLF(ctx context.Context, tlfIDStr chat1.TLFIDStr) (res chat1.LastActiveStatus, err error) {
  3615  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3616  	defer h.Trace(ctx, &err, "GetLastActiveForTLF")()
  3617  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3618  	if err != nil {
  3619  		return res, err
  3620  	}
  3621  	tlfID, err := chat1.MakeTLFID(tlfIDStr.String())
  3622  	if err != nil {
  3623  		return res, err
  3624  	}
  3625  	mtime, err := h.G().TeamChannelSource.GetLastActiveForTLF(ctx, uid, tlfID, chat1.TopicType_CHAT)
  3626  	if err != nil {
  3627  		return res, err
  3628  	}
  3629  	return utils.ToLastActiveStatus(mtime), nil
  3630  }
  3631  
  3632  func (h *Server) GetLastActiveForTeams(ctx context.Context) (res chat1.LastActiveStatusAll, err error) {
  3633  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3634  	defer h.Trace(ctx, &err, "GetLastActiveForTeams")()
  3635  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3636  	if err != nil {
  3637  		return res, err
  3638  	}
  3639  	activity, err := h.G().TeamChannelSource.GetLastActiveForTeams(ctx, uid, chat1.TopicType_CHAT)
  3640  	if err != nil {
  3641  		return res, err
  3642  	}
  3643  	res.Teams = make(map[chat1.TLFIDStr]chat1.LastActiveStatus)
  3644  	res.Channels = make(map[chat1.ConvIDStr]chat1.LastActiveStatus)
  3645  	for tlfID, mtime := range activity.Teams {
  3646  		res.Teams[tlfID] = utils.ToLastActiveStatus(mtime)
  3647  	}
  3648  	for convID, mtime := range activity.Channels {
  3649  		res.Channels[convID] = utils.ToLastActiveStatus(mtime)
  3650  	}
  3651  	return res, nil
  3652  }
  3653  
  3654  func (h *Server) GetRecentJoinsLocal(ctx context.Context, convID chat1.ConversationID) (numJoins int, err error) {
  3655  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3656  	defer h.Trace(ctx, &err, "GetRecentJoinsLocal")()
  3657  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  3658  	if err != nil {
  3659  		return 0, err
  3660  	}
  3661  	return h.G().TeamChannelSource.GetRecentJoins(ctx, convID, h.remoteClient())
  3662  }
  3663  
  3664  func (h *Server) RefreshParticipants(ctx context.Context, convID chat1.ConversationID) (err error) {
  3665  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3666  	defer h.Trace(ctx, &err, "RefreshParticipants")()
  3667  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3668  	if err != nil {
  3669  		return err
  3670  	}
  3671  	h.G().ParticipantsSource.GetWithNotifyNonblock(ctx, uid, convID, types.InboxSourceDataSourceAll)
  3672  	return nil
  3673  }
  3674  
  3675  func (h *Server) GetParticipants(ctx context.Context, convID chat1.ConversationID) (participants []chat1.ConversationLocalParticipant, err error) {
  3676  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_CLI, nil, h.identNotifier)
  3677  	defer h.Trace(ctx, &err, "GetParticipants")()
  3678  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3679  	if err != nil {
  3680  		return nil, err
  3681  	}
  3682  	uids, err := h.G().ParticipantsSource.Get(ctx, uid, convID, types.InboxSourceDataSourceAll)
  3683  	if err != nil {
  3684  		return nil, err
  3685  	}
  3686  	participants, err = h.G().ParticipantsSource.GetParticipantsFromUids(ctx, uids)
  3687  	if err != nil {
  3688  		return nil, err
  3689  	}
  3690  	return participants, nil
  3691  }
  3692  
  3693  func (h *Server) GetLastActiveAtLocal(ctx context.Context, arg chat1.GetLastActiveAtLocalArg) (lastActiveAt gregor1.Time, err error) {
  3694  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3695  	defer h.Trace(ctx, &err, "GetLastActiveAtLocal")()
  3696  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  3697  	if err != nil {
  3698  		return 0, err
  3699  	}
  3700  	uid := gregor1.UID(libkb.UsernameToUID(arg.Username).ToBytes())
  3701  	return h.G().TeamChannelSource.GetLastActiveAt(ctx, arg.TeamID, uid, h.remoteClient())
  3702  }
  3703  
  3704  func (h *Server) GetLastActiveAtMultiLocal(ctx context.Context, arg chat1.GetLastActiveAtMultiLocalArg) (res map[keybase1.TeamID]gregor1.Time, err error) {
  3705  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3706  	defer h.Trace(ctx, &err, "GetLastActiveAtMultiLocal")()
  3707  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  3708  	if err != nil {
  3709  		return nil, err
  3710  	}
  3711  	uid := gregor1.UID(libkb.UsernameToUID(arg.Username).ToBytes())
  3712  
  3713  	res = map[keybase1.TeamID]gregor1.Time{}
  3714  	resMu := sync.Mutex{}
  3715  	sem := semaphore.NewWeighted(10)
  3716  	eg, subctx := errgroup.WithContext(ctx)
  3717  	for _, teamID := range arg.TeamIDs {
  3718  		if err := sem.Acquire(subctx, 1); err != nil {
  3719  			return nil, err
  3720  		}
  3721  
  3722  		teamID := teamID
  3723  		eg.Go(func() error {
  3724  			defer sem.Release(1)
  3725  
  3726  			lastActive, err := h.G().TeamChannelSource.GetLastActiveAt(subctx, teamID, uid, h.remoteClient())
  3727  			if err != nil {
  3728  				return err
  3729  			}
  3730  			resMu.Lock()
  3731  			res[teamID] = lastActive
  3732  			resMu.Unlock()
  3733  			return nil
  3734  		})
  3735  	}
  3736  	if err := eg.Wait(); err != nil {
  3737  		return nil, err
  3738  	}
  3739  
  3740  	return res, nil
  3741  }
  3742  
  3743  func (h *Server) getEmojiError(err error) *chat1.EmojiError {
  3744  	if err == nil {
  3745  		return nil
  3746  	}
  3747  	if verr, ok := err.(*EmojiValidationError); ok {
  3748  		return verr.Export()
  3749  	}
  3750  	return &chat1.EmojiError{
  3751  		Clidisplay: err.Error(),
  3752  		Uidisplay:  "unknown error",
  3753  	}
  3754  }
  3755  
  3756  func (h *Server) AddEmoji(ctx context.Context, arg chat1.AddEmojiArg) (res chat1.AddEmojiRes, err error) {
  3757  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3758  	defer h.Trace(ctx, &err, "AddEmoji")()
  3759  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3760  	defer func() {
  3761  		res.Error = h.getEmojiError(err)
  3762  		err = nil
  3763  	}()
  3764  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3765  	if err != nil {
  3766  		return res, err
  3767  	}
  3768  	if _, err := h.G().EmojiSource.Add(ctx, uid, arg.ConvID, arg.Alias, arg.Filename, arg.AllowOverwrite); err != nil {
  3769  		return res, err
  3770  	}
  3771  	return res, nil
  3772  }
  3773  
  3774  func (h *Server) AddEmojis(ctx context.Context, arg chat1.AddEmojisArg) (res chat1.AddEmojisRes, err error) {
  3775  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3776  	defer h.Trace(ctx, &err, "AddEmojis")()
  3777  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3778  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3779  	if err != nil {
  3780  		return res, err
  3781  	}
  3782  	if len(arg.Aliases) != len(arg.Filenames) {
  3783  		return chat1.AddEmojisRes{}, errors.New("aliases and filenames have different length")
  3784  	}
  3785  	res.FailedFilenames = make(map[string]chat1.EmojiError)
  3786  	res.SuccessFilenames = make([]string, 0, len(arg.Aliases))
  3787  	for i := range arg.Aliases {
  3788  		_, err := h.G().EmojiSource.Add(ctx, uid, arg.ConvID, arg.Aliases[i], arg.Filenames[i], arg.AllowOverwrite[i])
  3789  		if err != nil {
  3790  			res.FailedFilenames[arg.Filenames[i]] = *h.getEmojiError(err)
  3791  		} else {
  3792  			res.SuccessFilenames = append(res.SuccessFilenames, arg.Filenames[i])
  3793  		}
  3794  	}
  3795  	return res, nil
  3796  }
  3797  
  3798  func (h *Server) AddEmojiAlias(ctx context.Context, arg chat1.AddEmojiAliasArg) (res chat1.AddEmojiAliasRes, err error) {
  3799  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3800  	defer h.Trace(ctx, &err, "AddEmojiAlias")()
  3801  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3802  	defer func() {
  3803  		res.Error = h.getEmojiError(err)
  3804  		err = nil
  3805  	}()
  3806  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3807  	if err != nil {
  3808  		return res, err
  3809  	}
  3810  	if _, err := h.G().EmojiSource.AddAlias(ctx, uid, arg.ConvID, arg.NewAlias, arg.ExistingAlias); err != nil {
  3811  		return res, err
  3812  	}
  3813  	return res, nil
  3814  }
  3815  
  3816  func (h *Server) RemoveEmoji(ctx context.Context, arg chat1.RemoveEmojiArg) (res chat1.RemoveEmojiRes, err error) {
  3817  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3818  	defer h.Trace(ctx, &err, "RemoveEmoji")()
  3819  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3820  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3821  	if err != nil {
  3822  		return res, err
  3823  	}
  3824  	if err := h.G().EmojiSource.Remove(ctx, uid, arg.ConvID, arg.Alias); err != nil {
  3825  		return res, err
  3826  	}
  3827  	return res, nil
  3828  }
  3829  
  3830  func (h *Server) UserEmojis(ctx context.Context, arg chat1.UserEmojisArg) (res chat1.UserEmojiRes, err error) {
  3831  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3832  	defer h.Trace(ctx, &err, "UserEmojis")()
  3833  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3834  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3835  	if err != nil {
  3836  		return res, err
  3837  	}
  3838  	res.Emojis, err = h.G().EmojiSource.Get(ctx, uid, arg.ConvID, arg.Opts)
  3839  	return res, err
  3840  }
  3841  
  3842  func (h *Server) ToggleEmojiAnimations(ctx context.Context, enabled bool) (err error) {
  3843  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
  3844  	defer h.Trace(ctx, &err, "ToggleEmojiAnimations")()
  3845  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3846  	if err != nil {
  3847  		return err
  3848  	}
  3849  	return h.G().EmojiSource.ToggleAnimations(ctx, uid, enabled)
  3850  }
  3851  
  3852  func (h *Server) ForwardMessage(ctx context.Context, arg chat1.ForwardMessageArg) (res chat1.PostLocalRes, err error) {
  3853  	var identBreaks []keybase1.TLFIdentifyFailure
  3854  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  3855  	defer h.Trace(ctx, &err, "ForwardMessage")()
  3856  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3857  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3858  	if err != nil {
  3859  		return res, err
  3860  	}
  3861  	reason := chat1.GetThreadReason_FORWARDMSG
  3862  	msg, err := h.G().ConvSource.GetMessage(ctx, arg.SrcConvID, uid, arg.MsgID, &reason, nil, true)
  3863  	if err != nil {
  3864  		return res, err
  3865  	} else if !msg.IsValid() {
  3866  		return res, fmt.Errorf("unable to foward message, source is invalid")
  3867  	}
  3868  	dstConv, err := utils.GetVerifiedConv(ctx, h.G(), uid, arg.DstConvID, types.InboxSourceDataSourceAll)
  3869  	if err != nil {
  3870  		return res, err
  3871  	}
  3872  	mvalid := msg.Valid()
  3873  	switch mvalid.ClientHeader.MessageType {
  3874  	case chat1.MessageType_ATTACHMENT:
  3875  		// download from the original source
  3876  		mbod := msg.Valid().MessageBody.Attachment()
  3877  		sink, _, err := h.G().AttachmentUploader.GetUploadTempSink(ctx, mbod.Object.Filename)
  3878  		if err != nil {
  3879  			return res, err
  3880  		}
  3881  		_, err = h.downloadAttachmentLocal(ctx, uid, downloadAttachmentArg{
  3882  			SessionID:        arg.SessionID,
  3883  			ConversationID:   arg.SrcConvID,
  3884  			MessageID:        arg.MsgID,
  3885  			IdentifyBehavior: arg.IdentifyBehavior,
  3886  			Sink:             sink,
  3887  		})
  3888  		if err != nil {
  3889  			return res, err
  3890  		}
  3891  		var ephemeralLifetime *gregor1.DurationSec
  3892  		if md := mvalid.EphemeralMetadata(); md != nil {
  3893  			ephemeralLifetime = &md.Lifetime
  3894  		}
  3895  		return h.PostFileAttachmentLocal(ctx, chat1.PostFileAttachmentLocalArg{
  3896  			SessionID: arg.SessionID,
  3897  			Arg: chat1.PostFileAttachmentArg{
  3898  				ConversationID:    arg.DstConvID,
  3899  				TlfName:           dstConv.Info.TlfName,
  3900  				Visibility:        dstConv.Info.Visibility,
  3901  				Filename:          sink.Name(),
  3902  				Title:             arg.Title,
  3903  				Metadata:          mbod.Metadata,
  3904  				IdentifyBehavior:  arg.IdentifyBehavior,
  3905  				EphemeralLifetime: ephemeralLifetime,
  3906  			},
  3907  		})
  3908  	default:
  3909  		return h.PostLocal(ctx, chat1.PostLocalArg{
  3910  			SessionID:      arg.SessionID,
  3911  			ConversationID: arg.DstConvID,
  3912  			Msg: chat1.MessagePlaintext{
  3913  				ClientHeader: chat1.MessageClientHeader{
  3914  					Conv:              dstConv.Info.Triple,
  3915  					TlfName:           dstConv.Info.TlfName,
  3916  					TlfPublic:         dstConv.Info.Visibility == keybase1.TLFVisibility_PUBLIC,
  3917  					MessageType:       mvalid.ClientHeader.MessageType,
  3918  					EphemeralMetadata: mvalid.EphemeralMetadata(),
  3919  				},
  3920  				MessageBody: mvalid.MessageBody.DeepCopy(),
  3921  			},
  3922  			IdentifyBehavior:   arg.IdentifyBehavior,
  3923  			SkipInChatPayments: true,
  3924  		})
  3925  	}
  3926  }
  3927  
  3928  func (h *Server) ForwardMessageNonblock(ctx context.Context, arg chat1.ForwardMessageNonblockArg) (res chat1.PostLocalNonblockRes, err error) {
  3929  	var identBreaks []keybase1.TLFIdentifyFailure
  3930  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks, h.identNotifier)
  3931  	defer h.Trace(ctx, &err, "ForwardMessageNonblock")()
  3932  	defer h.suspendBgConvLoads(ctx)()
  3933  	defer func() { h.setResultRateLimit(ctx, &res) }()
  3934  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  3935  	if err != nil {
  3936  		return res, err
  3937  	}
  3938  	reason := chat1.GetThreadReason_FORWARDMSG
  3939  	msg, err := h.G().ConvSource.GetMessage(ctx, arg.SrcConvID, uid, arg.MsgID, &reason, nil, true)
  3940  	if err != nil {
  3941  		return res, err
  3942  	} else if !msg.IsValid() {
  3943  		return res, fmt.Errorf("unable to forward message, source is invalid")
  3944  	}
  3945  	dstConv, err := utils.GetVerifiedConv(ctx, h.G(), uid, arg.DstConvID, types.InboxSourceDataSourceAll)
  3946  	if err != nil {
  3947  		return res, err
  3948  	}
  3949  	mvalid := msg.Valid()
  3950  	switch mvalid.ClientHeader.MessageType {
  3951  	case chat1.MessageType_ATTACHMENT:
  3952  		mbod := msg.Valid().MessageBody.Attachment()
  3953  		sink, _, err := h.G().AttachmentUploader.GetUploadTempSink(ctx, mbod.Object.Filename)
  3954  		if err != nil {
  3955  			return res, err
  3956  		}
  3957  		_, err = h.downloadAttachmentLocal(ctx, uid, downloadAttachmentArg{
  3958  			SessionID:        arg.SessionID,
  3959  			ConversationID:   arg.SrcConvID,
  3960  			MessageID:        arg.MsgID,
  3961  			IdentifyBehavior: arg.IdentifyBehavior,
  3962  			Sink:             sink,
  3963  		})
  3964  		if err != nil {
  3965  			return res, err
  3966  		}
  3967  		var ephemeralLifetime *gregor1.DurationSec
  3968  		if md := mvalid.EphemeralMetadata(); md != nil {
  3969  			ephemeralLifetime = &md.Lifetime
  3970  		}
  3971  		return h.PostFileAttachmentLocalNonblock(ctx, chat1.PostFileAttachmentLocalNonblockArg{
  3972  			SessionID: arg.SessionID,
  3973  			Arg: chat1.PostFileAttachmentArg{
  3974  				ConversationID:    arg.DstConvID,
  3975  				TlfName:           dstConv.Info.TlfName,
  3976  				Visibility:        dstConv.Info.Visibility,
  3977  				Filename:          sink.Name(),
  3978  				Title:             arg.Title,
  3979  				Metadata:          mbod.Metadata,
  3980  				IdentifyBehavior:  arg.IdentifyBehavior,
  3981  				EphemeralLifetime: ephemeralLifetime,
  3982  			},
  3983  		})
  3984  	default:
  3985  		return h.PostLocalNonblock(ctx, chat1.PostLocalNonblockArg{
  3986  			SessionID:      arg.SessionID,
  3987  			ConversationID: arg.DstConvID,
  3988  			Msg: chat1.MessagePlaintext{
  3989  				ClientHeader: chat1.MessageClientHeader{
  3990  					Conv:              dstConv.Info.Triple,
  3991  					TlfName:           dstConv.Info.TlfName,
  3992  					TlfPublic:         dstConv.Info.Visibility == keybase1.TLFVisibility_PUBLIC,
  3993  					MessageType:       mvalid.ClientHeader.MessageType,
  3994  					EphemeralMetadata: mvalid.EphemeralMetadata(),
  3995  				},
  3996  				MessageBody: mvalid.MessageBody.DeepCopy(),
  3997  			},
  3998  			IdentifyBehavior:   arg.IdentifyBehavior,
  3999  			SkipInChatPayments: true,
  4000  		})
  4001  	}
  4002  }
  4003  
  4004  func (h *Server) ForwardMessageConvSearch(ctx context.Context, term string) (res []chat1.ConvSearchHit, err error) {
  4005  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil)
  4006  	defer h.Trace(ctx, &err, "ForwardMessageConvSearch")()
  4007  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  4008  	if err != nil {
  4009  		return res, err
  4010  	}
  4011  	username := h.G().GetEnv().GetUsername().String()
  4012  	allConvs, err := h.G().InboxSource.Search(ctx, uid, term, 100, types.InboxSourceSearchEmptyModeAllBySendCtime)
  4013  	if err != nil {
  4014  		return res, err
  4015  	}
  4016  	res = make([]chat1.ConvSearchHit, 0, len(allConvs))
  4017  	for _, conv := range allConvs {
  4018  		if conv.CannotWrite() {
  4019  			continue
  4020  		}
  4021  		switch conv.GetTeamType() {
  4022  		case chat1.TeamType_NONE:
  4023  			searchable := utils.SearchableRemoteConversationName(conv, username)
  4024  			res = append(res, chat1.ConvSearchHit{
  4025  				Name:   searchable,
  4026  				ConvID: conv.GetConvID(),
  4027  				Parts:  strings.Split(searchable, ","),
  4028  			})
  4029  		case chat1.TeamType_SIMPLE,
  4030  			chat1.TeamType_COMPLEX:
  4031  			res = append(res, chat1.ConvSearchHit{
  4032  				Name:   utils.SearchableRemoteConversationName(conv, username),
  4033  				ConvID: conv.GetConvID(),
  4034  				IsTeam: true,
  4035  			})
  4036  		}
  4037  	}
  4038  	return res, nil
  4039  }
  4040  
  4041  func (h *Server) TrackGiphySelect(ctx context.Context, arg chat1.TrackGiphySelectArg) (res chat1.TrackGiphySelectRes, err error) {
  4042  	var identBreaks []keybase1.TLFIdentifyFailure
  4043  	ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks,
  4044  		h.identNotifier)
  4045  	// Never log user content.
  4046  	defer h.Trace(ctx, &err, "TrackGiphySelect")()
  4047  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  4048  	if err != nil {
  4049  		h.Debug(ctx, "TrackGiphySelect: not logged in: %s", err)
  4050  		return chat1.TrackGiphySelectRes{}, nil
  4051  	}
  4052  	err = storage.NewGiphyStore(h.G()).Put(ctx, uid, arg.Result)
  4053  	return chat1.TrackGiphySelectRes{}, err
  4054  }
  4055  
  4056  func (h *Server) ArchiveChat(ctx context.Context, arg chat1.ArchiveChatJobRequest) (res chat1.ArchiveChatRes, err error) {
  4057  	var identBreaks []keybase1.TLFIdentifyFailure
  4058  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks,
  4059  		h.identNotifier)
  4060  	defer h.Trace(ctx, &err, "ArchiveChat")()
  4061  	uid, err := utils.AssertLoggedInUID(ctx, h.G())
  4062  	if err != nil {
  4063  		h.Debug(ctx, "ArchiveChat: not logged in: %s", err)
  4064  		return chat1.ArchiveChatRes{}, nil
  4065  	}
  4066  
  4067  	outpath, err := NewChatArchiver(h.G(), uid, h.remoteClient).ArchiveChat(ctx, arg)
  4068  	if err != nil {
  4069  		return chat1.ArchiveChatRes{}, err
  4070  	}
  4071  
  4072  	return chat1.ArchiveChatRes{
  4073  		IdentifyFailures: identBreaks,
  4074  		OutputPath:       outpath,
  4075  	}, err
  4076  }
  4077  
  4078  func (h *Server) ArchiveChatList(ctx context.Context, identifyBehavior keybase1.TLFIdentifyBehavior) (res chat1.ArchiveChatListRes, err error) {
  4079  	var identBreaks []keybase1.TLFIdentifyFailure
  4080  	ctx = globals.ChatCtx(ctx, h.G(), identifyBehavior, &identBreaks,
  4081  		h.identNotifier)
  4082  	defer h.Trace(ctx, &err, "ArchiveChatList")()
  4083  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  4084  	if err != nil {
  4085  		h.Debug(ctx, "ArchiveChatList: not logged in: %s", err)
  4086  		return chat1.ArchiveChatListRes{}, nil
  4087  	}
  4088  
  4089  	return h.G().ArchiveRegistry.List(ctx)
  4090  }
  4091  
  4092  func (h *Server) ArchiveChatDelete(ctx context.Context, arg chat1.ArchiveChatDeleteArg) (err error) {
  4093  	var identBreaks []keybase1.TLFIdentifyFailure
  4094  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks,
  4095  		h.identNotifier)
  4096  	defer h.Trace(ctx, &err, "ArchiveChatDelete")()
  4097  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  4098  	if err != nil {
  4099  		h.Debug(ctx, "ArchiveChatDelete: not logged in: %s", err)
  4100  		return nil
  4101  	}
  4102  
  4103  	return h.G().ArchiveRegistry.Delete(ctx, arg.JobID, arg.DeleteOutputPath)
  4104  }
  4105  
  4106  func (h *Server) ArchiveChatPause(ctx context.Context, arg chat1.ArchiveChatPauseArg) (err error) {
  4107  	var identBreaks []keybase1.TLFIdentifyFailure
  4108  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks,
  4109  		h.identNotifier)
  4110  	defer h.Trace(ctx, &err, "ArchiveChatPause")()
  4111  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  4112  	if err != nil {
  4113  		h.Debug(ctx, "ArchiveChatPause: not logged in: %s", err)
  4114  		return nil
  4115  	}
  4116  
  4117  	return h.G().ArchiveRegistry.Pause(ctx, arg.JobID)
  4118  }
  4119  
  4120  func (h *Server) ArchiveChatResume(ctx context.Context, arg chat1.ArchiveChatResumeArg) (err error) {
  4121  	var identBreaks []keybase1.TLFIdentifyFailure
  4122  	ctx = globals.ChatCtx(ctx, h.G(), arg.IdentifyBehavior, &identBreaks,
  4123  		h.identNotifier)
  4124  	defer h.Trace(ctx, &err, "ArchiveChatResume")()
  4125  	_, err = utils.AssertLoggedInUID(ctx, h.G())
  4126  	if err != nil {
  4127  		h.Debug(ctx, "ArchiveChatResume: not logged in: %s", err)
  4128  		return nil
  4129  	}
  4130  
  4131  	return h.G().ArchiveRegistry.Resume(ctx, arg.JobID)
  4132  }