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 }