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

     1  package chat
     2  
     3  import (
     4  	"encoding/hex"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/keybase/client/go/chat/attachments"
    10  	"github.com/keybase/client/go/chat/bots"
    11  	"github.com/keybase/client/go/chat/globals"
    12  	"github.com/keybase/client/go/chat/msgchecker"
    13  	"github.com/keybase/client/go/chat/storage"
    14  	"github.com/keybase/client/go/chat/types"
    15  	"github.com/keybase/client/go/chat/utils"
    16  	"github.com/keybase/client/go/engine"
    17  	"github.com/keybase/client/go/libkb"
    18  	"github.com/keybase/client/go/protocol/chat1"
    19  	"github.com/keybase/client/go/protocol/gregor1"
    20  	"github.com/keybase/client/go/protocol/keybase1"
    21  	"github.com/keybase/client/go/teams"
    22  	"github.com/keybase/clockwork"
    23  	context "golang.org/x/net/context"
    24  )
    25  
    26  type BlockingSender struct {
    27  	globals.Contextified
    28  	utils.DebugLabeler
    29  
    30  	boxer             *Boxer
    31  	store             attachments.Store
    32  	getRi             func() chat1.RemoteInterface
    33  	prevPtrPagination *chat1.Pagination
    34  	clock             clockwork.Clock
    35  }
    36  
    37  var _ types.Sender = (*BlockingSender)(nil)
    38  
    39  func NewBlockingSender(g *globals.Context, boxer *Boxer, getRi func() chat1.RemoteInterface) *BlockingSender {
    40  	return &BlockingSender{
    41  		Contextified:      globals.NewContextified(g),
    42  		DebugLabeler:      utils.NewDebugLabeler(g.ExternalG(), "BlockingSender", false),
    43  		getRi:             getRi,
    44  		boxer:             boxer,
    45  		store:             attachments.NewS3Store(g, g.GetRuntimeDir()),
    46  		clock:             clockwork.NewRealClock(),
    47  		prevPtrPagination: &chat1.Pagination{Num: 50},
    48  	}
    49  }
    50  
    51  func (s *BlockingSender) setPrevPagination(p *chat1.Pagination) {
    52  	s.prevPtrPagination = p
    53  }
    54  
    55  func (s *BlockingSender) SetClock(clock clockwork.Clock) {
    56  	s.clock = clock
    57  }
    58  
    59  func (s *BlockingSender) addSenderToMessage(msg chat1.MessagePlaintext) (chat1.MessagePlaintext, gregor1.UID, error) {
    60  	uid := s.G().Env.GetUID()
    61  	if uid.IsNil() {
    62  		return chat1.MessagePlaintext{}, nil, libkb.LoginRequiredError{}
    63  	}
    64  	did := s.G().Env.GetDeviceID()
    65  	if did.IsNil() {
    66  		return chat1.MessagePlaintext{}, nil, libkb.DeviceRequiredError{}
    67  	}
    68  
    69  	huid := uid.ToBytes()
    70  	if huid == nil {
    71  		return chat1.MessagePlaintext{}, nil, errors.New("invalid UID")
    72  	}
    73  
    74  	hdid := make([]byte, libkb.DeviceIDLen)
    75  	if err := did.ToBytes(hdid); err != nil {
    76  		return chat1.MessagePlaintext{}, nil, err
    77  	}
    78  
    79  	header := msg.ClientHeader
    80  	header.Sender = gregor1.UID(huid)
    81  	header.SenderDevice = gregor1.DeviceID(hdid)
    82  	updated := chat1.MessagePlaintext{
    83  		ClientHeader:       header,
    84  		MessageBody:        msg.MessageBody,
    85  		SupersedesOutboxID: msg.SupersedesOutboxID,
    86  	}
    87  	return updated, gregor1.UID(huid), nil
    88  }
    89  
    90  func (s *BlockingSender) addPrevPointersAndCheckConvID(ctx context.Context, msg chat1.MessagePlaintext,
    91  	conv chat1.ConversationLocal) (resMsg chat1.MessagePlaintext, err error) {
    92  
    93  	// Make sure the caller hasn't already assembled this list. For now, this
    94  	// should never happen, and we'll return an error just in case we make a
    95  	// mistake in the future. But if there's some use case in the future where
    96  	// a caller wants to specify custom prevs, we can relax this.
    97  	if len(msg.ClientHeader.Prev) != 0 {
    98  		return resMsg, fmt.Errorf("addPrevPointersToMessage expects an empty prev list")
    99  	}
   100  
   101  	var thread chat1.ThreadView
   102  	var prevs []chat1.MessagePreviousPointer
   103  	pagination := &chat1.Pagination{
   104  		Num: s.prevPtrPagination.Num,
   105  	}
   106  	// If we fail to find anything to prev against after maxAttempts, we allow
   107  	// the message to be send with an empty prev list.
   108  	maxAttempts := 5
   109  	attempt := 0
   110  	reachedLast := false
   111  	for {
   112  		thread, err = s.G().ConvSource.Pull(ctx, conv.GetConvID(), msg.ClientHeader.Sender,
   113  			chat1.GetThreadReason_PREPARE, nil,
   114  			&chat1.GetThreadQuery{
   115  				DisableResolveSupersedes: true,
   116  			},
   117  			pagination)
   118  		if err != nil {
   119  			return resMsg, err
   120  		} else if thread.Pagination == nil {
   121  			break
   122  		}
   123  		pagination.Next = thread.Pagination.Next
   124  
   125  		if len(thread.Messages) == 0 {
   126  			s.Debug(ctx, "no local messages found for prev pointers")
   127  		}
   128  		newPrevsForRegular, newPrevsForExploding, err := CheckPrevPointersAndGetUnpreved(&thread)
   129  		if err != nil {
   130  			return resMsg, err
   131  		}
   132  
   133  		var hasPrev bool
   134  		if msg.IsEphemeral() {
   135  			prevs = newPrevsForExploding
   136  			hasPrev = len(newPrevsForExploding) > 0
   137  		} else {
   138  			prevs = newPrevsForRegular
   139  			// If we have only sent ephemeralMessages and are now sending a regular
   140  			// message, we may have an empty list for newPrevsForRegular. In this
   141  			// case we allow the `Prev` to be empty, so we don't want to abort in
   142  			// the check on numPrev below.
   143  			hasPrev = len(newPrevsForRegular) > 0 || len(newPrevsForExploding) > 0
   144  		}
   145  
   146  		if hasPrev {
   147  			break
   148  		} else if thread.Pagination.Last && !reachedLast {
   149  			s.Debug(ctx, "Could not find previous messages for prev pointers (of %v). Nuking local storage and retrying.", len(thread.Messages))
   150  			if err := s.G().ConvSource.Clear(ctx, conv.GetConvID(), msg.ClientHeader.Sender, &types.ClearOpts{
   151  				SendLocalAdminNotification: true,
   152  				Reason:                     "missing prev pointer",
   153  			}); err != nil {
   154  				s.Debug(ctx, "Unable to clear conversation: %v, %v", conv.GetConvID(), err)
   155  				break
   156  			}
   157  			attempt = 0
   158  			pagination.Next = nil
   159  			// Make sure we only reset `attempt` once
   160  			reachedLast = true
   161  			continue
   162  		} else if attempt >= maxAttempts || reachedLast {
   163  			s.Debug(ctx, "Could not find previous messages for prev pointers (of %v), after %v attempts. Giving up.", len(thread.Messages), attempt)
   164  			break
   165  		} else {
   166  			s.Debug(ctx, "Could not find previous messages for prev pointers (of %v), attempt: %v of %v, retrying", len(thread.Messages), attempt, maxAttempts)
   167  		}
   168  		attempt++
   169  	}
   170  
   171  	for _, msg2 := range thread.Messages {
   172  		if msg2.IsValid() {
   173  			if err = s.checkConvID(ctx, conv, msg, msg2); err != nil {
   174  				s.Debug(ctx, "Unable to checkConvID: %s", msg2.DebugString())
   175  				return resMsg, err
   176  			}
   177  			break
   178  		}
   179  	}
   180  
   181  	// Make an attempt to avoid changing anything in the input message. There
   182  	// are a lot of shared pointers though, so this is
   183  	header := msg.ClientHeader
   184  	header.Prev = prevs
   185  	updated := chat1.MessagePlaintext{
   186  		ClientHeader: header,
   187  		MessageBody:  msg.MessageBody,
   188  	}
   189  	return updated, nil
   190  }
   191  
   192  // Check that the {ConvID,ConvTriple,TlfName} of msgToSend matches both the ConvID and an existing message from the questionable ConvID.
   193  // `convID` is the convID that `msgToSend` will be posted to.
   194  // `msgReference` is a validated message from `convID`.
   195  // The misstep that this method checks for is thus: The frontend may post a message while viewing an "untrusted inbox view".
   196  // That message (msgToSend) will have the header.{TlfName,TlfPublic} set to the user's intention.
   197  // But the header.Conv.{Tlfid,TopicType,TopicID} and the convID to post to may be erroneously set to a different conversation's values.
   198  // This method checks that all of those fields match. Using `msgReference` as the validated link from {TlfName,TlfPublic} <-> ConvTriple.
   199  func (s *BlockingSender) checkConvID(ctx context.Context, conv chat1.ConversationLocal,
   200  	msgToSend chat1.MessagePlaintext, msgReference chat1.MessageUnboxed) error {
   201  
   202  	headerQ := msgToSend.ClientHeader
   203  	headerRef := msgReference.Valid().ClientHeader
   204  
   205  	fmtConv := func(conv chat1.ConversationIDTriple) string { return hex.EncodeToString(conv.Hash()) }
   206  
   207  	if !headerQ.Conv.Derivable(conv.GetConvID()) {
   208  		s.Debug(ctx, "checkConvID: ConvID %s </- %s", fmtConv(headerQ.Conv), conv.GetConvID())
   209  		return fmt.Errorf("ConversationID does not match reference message")
   210  	}
   211  
   212  	if !headerQ.Conv.Eq(headerRef.Conv) {
   213  		s.Debug(ctx, "checkConvID: Conv %s != %s", fmtConv(headerQ.Conv), fmtConv(headerRef.Conv))
   214  		return fmt.Errorf("ConversationID does not match reference message")
   215  	}
   216  
   217  	if headerQ.TlfPublic != headerRef.TlfPublic {
   218  		s.Debug(ctx, "checkConvID: TlfPublic %s != %s", headerQ.TlfPublic, headerRef.TlfPublic)
   219  		return fmt.Errorf("Chat public-ness does not match reference message")
   220  	}
   221  	if headerQ.TlfName != headerRef.TlfName {
   222  		// If we're of type TEAM, we lookup the name info for the team and
   223  		// verify it matches what is on the message itself. If we rename a
   224  		// subteam the names between the current and reference message will
   225  		// differ so we cannot rely on that.
   226  		switch conv.GetMembersType() {
   227  		case chat1.ConversationMembersType_TEAM:
   228  			// Cannonicalize the given TlfName
   229  			teamNameParsed, err := keybase1.TeamNameFromString(headerQ.TlfName)
   230  			if err != nil {
   231  				return fmt.Errorf("invalid team name: %v", err)
   232  			}
   233  			if info, err := CreateNameInfoSource(ctx, s.G(), conv.GetMembersType()).LookupName(ctx,
   234  				conv.Info.Triple.Tlfid,
   235  				conv.Info.Visibility == keybase1.TLFVisibility_PUBLIC,
   236  				headerQ.TlfName); err != nil {
   237  				return err
   238  			} else if info.CanonicalName != teamNameParsed.String() {
   239  				return fmt.Errorf("TlfName does not match conversation tlf [%q vs ref %q]", teamNameParsed.String(), info.CanonicalName)
   240  			}
   241  		default:
   242  			// Try normalizing both tlfnames if simple comparison fails because they may have resolved.
   243  			if namesEq, err := s.boxer.CompareTlfNames(ctx, headerQ.TlfName, headerRef.TlfName,
   244  				conv.GetMembersType(), headerQ.TlfPublic); err != nil {
   245  				return err
   246  			} else if !namesEq {
   247  				s.Debug(ctx, "checkConvID: TlfName %s != %s", headerQ.TlfName, headerRef.TlfName)
   248  				return fmt.Errorf("TlfName does not match reference message [%q vs ref %q]", headerQ.TlfName, headerRef.TlfName)
   249  			}
   250  		}
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // Get all messages to be deleted, and attachments to delete.
   257  // Returns (message, assetsToDelete, flipConvToDelete, error)
   258  // If the entire conversation is cached locally, this will find all messages that should be deleted.
   259  // If the conversation is not cached, this relies on the server to get old messages, so the server
   260  // could omit messages. Those messages would then not be signed into the `Deletes` list. And their
   261  // associated attachment assets would be left undeleted.
   262  func (s *BlockingSender) getAllDeletedEdits(ctx context.Context, uid gregor1.UID,
   263  	convID chat1.ConversationID, msg chat1.MessagePlaintext) (chat1.MessagePlaintext, []chat1.Asset, *chat1.ConversationID, error) {
   264  
   265  	var pendingAssetDeletes []chat1.Asset
   266  	var deleteFlipConvID *chat1.ConversationID
   267  
   268  	// Make sure this is a valid delete message
   269  	if msg.ClientHeader.MessageType != chat1.MessageType_DELETE {
   270  		return msg, nil, nil, nil
   271  	}
   272  
   273  	deleteTargetID := msg.ClientHeader.Supersedes
   274  	if deleteTargetID == 0 {
   275  		return msg, nil, nil, fmt.Errorf("getAllDeletedEdits: no supersedes specified")
   276  	}
   277  
   278  	// Get the one message to be deleted by ID.
   279  	deleteTarget, err := s.getMessage(ctx, uid, convID, deleteTargetID, false /* resolveSupersedes */)
   280  	if err != nil {
   281  		return msg, nil, nil, err
   282  	}
   283  	bodyTyp, err := deleteTarget.MessageBody.MessageType()
   284  	if err != nil {
   285  		return msg, nil, nil, err
   286  	}
   287  	switch bodyTyp {
   288  	case chat1.MessageType_REACTION:
   289  		// Don't do anything here for reactions/unfurls, they can't be edited
   290  		return msg, nil, nil, nil
   291  	case chat1.MessageType_SYSTEM:
   292  		msgSys := deleteTarget.MessageBody.System()
   293  		typ, err := msgSys.SystemType()
   294  		if err != nil {
   295  			return msg, nil, nil, err
   296  		}
   297  		if !chat1.IsSystemMsgDeletableByDelete(typ) {
   298  			return msg, nil, nil, fmt.Errorf("%v is not deletable", typ)
   299  		}
   300  	case chat1.MessageType_FLIP:
   301  		flipConvID := deleteTarget.MessageBody.Flip().FlipConvID
   302  		deleteFlipConvID = &flipConvID
   303  	}
   304  
   305  	// Delete all assets on the deleted message.
   306  	// assetsForMessage logs instead of failing.
   307  	pads2 := utils.AssetsForMessage(s.G(), deleteTarget.MessageBody)
   308  	pendingAssetDeletes = append(pendingAssetDeletes, pads2...)
   309  
   310  	// Time of the first message to be deleted.
   311  	timeOfFirst := gregor1.FromTime(deleteTarget.ServerHeader.Ctime)
   312  	// Time a couple seconds before that, because After querying is exclusive.
   313  	timeBeforeFirst := gregor1.ToTime(timeOfFirst.Add(-2 * time.Second))
   314  
   315  	// Get all the affected edits/AUs since just before the delete target.
   316  	// Use ConvSource with an `After` which query. Fetches from a combination of local cache
   317  	// and the server. This is an opportunity for the server to retain messages that should
   318  	// have been deleted without getting caught.
   319  	var tv chat1.ThreadView
   320  	switch deleteTarget.ClientHeader.MessageType {
   321  	case chat1.MessageType_UNFURL:
   322  		// no edits/deletes possible here
   323  	default:
   324  		tv, err = s.G().ConvSource.Pull(ctx, convID, msg.ClientHeader.Sender,
   325  			chat1.GetThreadReason_PREPARE, nil,
   326  			&chat1.GetThreadQuery{
   327  				MarkAsRead:   false,
   328  				MessageTypes: []chat1.MessageType{chat1.MessageType_EDIT, chat1.MessageType_ATTACHMENTUPLOADED},
   329  				After:        &timeBeforeFirst,
   330  			}, nil)
   331  		if err != nil {
   332  			return msg, nil, nil, err
   333  		}
   334  	}
   335  
   336  	// Get all affected messages to be deleted
   337  	deletes := []chat1.MessageID{deleteTargetID}
   338  	// Add in any reaction/unfurl messages the deleteTargetID may have
   339  	deletes = append(deletes,
   340  		append(deleteTarget.ServerHeader.ReactionIDs, deleteTarget.ServerHeader.UnfurlIDs...)...)
   341  	for _, m := range tv.Messages {
   342  		if !m.IsValid() {
   343  			continue
   344  		}
   345  		body := m.Valid().MessageBody
   346  		typ, err := body.MessageType()
   347  		if err != nil {
   348  			s.Debug(ctx, "getAllDeletedEdits: error getting message type: convID: %s msgID: %d err: %s",
   349  				convID, m.GetMessageID(), err.Error())
   350  			continue
   351  		}
   352  		switch typ {
   353  		case chat1.MessageType_EDIT:
   354  			if body.Edit().MessageID == deleteTargetID {
   355  				deletes = append(deletes, m.GetMessageID())
   356  			}
   357  		case chat1.MessageType_ATTACHMENTUPLOADED:
   358  			if body.Attachmentuploaded().MessageID == deleteTargetID {
   359  				deletes = append(deletes, m.GetMessageID())
   360  
   361  				// Delete all assets on AttachmentUploaded's for the deleted message.
   362  				// assetsForMessage logs instead of failing.
   363  				pads2 = utils.AssetsForMessage(s.G(), body)
   364  				pendingAssetDeletes = append(pendingAssetDeletes, pads2...)
   365  			}
   366  		default:
   367  			s.Debug(ctx, "getAllDeletedEdits: unexpected message type: convID: %s msgID: %d typ: %v",
   368  				convID, m.GetMessageID(), typ)
   369  			continue
   370  		}
   371  	}
   372  
   373  	// Modify original delete message
   374  	msg.ClientHeader.Deletes = deletes
   375  	// NOTE: If we ever add more fields to MessageDelete, we'll need to be
   376  	//       careful to preserve them here.
   377  	msg.MessageBody = chat1.NewMessageBodyWithDelete(chat1.MessageDelete{MessageIDs: deletes})
   378  
   379  	return msg, pendingAssetDeletes, deleteFlipConvID, nil
   380  }
   381  
   382  func (s *BlockingSender) getMessage(ctx context.Context, uid gregor1.UID,
   383  	convID chat1.ConversationID, msgID chat1.MessageID, resolveSupersedes bool) (mvalid chat1.MessageUnboxedValid, err error) {
   384  	reason := chat1.GetThreadReason_PREPARE
   385  	messages, err := s.G().ConvSource.GetMessages(ctx, convID, uid, []chat1.MessageID{msgID},
   386  		&reason, nil, resolveSupersedes)
   387  	if err != nil {
   388  		return mvalid, err
   389  	}
   390  	if len(messages) == 0 {
   391  		return mvalid, fmt.Errorf("getMessage: message not found")
   392  	}
   393  	if !messages[0].IsValid() {
   394  		st, err := messages[0].State()
   395  		return mvalid, fmt.Errorf("getMessage returned invalid message: msgID: %v st: %v: err %v",
   396  			msgID, st, err)
   397  	}
   398  	return messages[0].Valid(), nil
   399  }
   400  
   401  // If we are superseding an ephemeral message, we have to set the
   402  // ephemeralMetadata on this superseder message.
   403  func (s *BlockingSender) getSupersederEphemeralMetadata(ctx context.Context, uid gregor1.UID,
   404  	convID chat1.ConversationID, msg chat1.MessagePlaintext) (metadata *chat1.MsgEphemeralMetadata, err error) {
   405  
   406  	if chat1.IsEphemeralNonSupersederType(msg.ClientHeader.MessageType) {
   407  		// Leave whatever was previously set
   408  		return msg.ClientHeader.EphemeralMetadata, nil
   409  	} else if !chat1.IsEphemeralSupersederType(msg.ClientHeader.MessageType) {
   410  		// clear out any defaults, this msg is a non-ephemeral type
   411  		return nil, nil
   412  	}
   413  
   414  	supersededMsg, err := s.getMessage(ctx, uid, convID, msg.ClientHeader.Supersedes, false /* resolveSupersedes */)
   415  	if err != nil {
   416  		return nil, err
   417  	}
   418  	if supersededMsg.IsEphemeral() {
   419  		metadata = supersededMsg.EphemeralMetadata()
   420  		metadata.Lifetime = gregor1.ToDurationSec(supersededMsg.RemainingEphemeralLifetime(s.clock.Now()))
   421  	}
   422  	return metadata, nil
   423  }
   424  
   425  // processReactionMessage determines if we are trying to post a duplicate
   426  // chat1.MessageType_REACTION, which is considered a chat1.MessageType_DELETE
   427  // and updates the send appropriately.
   428  func (s *BlockingSender) processReactionMessage(ctx context.Context, uid gregor1.UID,
   429  	convID chat1.ConversationID, msg chat1.MessagePlaintext) (clientHeader chat1.MessageClientHeader, body chat1.MessageBody, err error) {
   430  	if msg.ClientHeader.MessageType != chat1.MessageType_REACTION {
   431  		// nothing to do here
   432  		return msg.ClientHeader, msg.MessageBody, nil
   433  	}
   434  
   435  	// We could either be posting a reaction or removing one that we already posted.
   436  	supersededMsg, err := s.getMessage(ctx, uid, convID, msg.ClientHeader.Supersedes,
   437  		true /* resolveSupersedes */)
   438  	if err != nil {
   439  		return clientHeader, body, err
   440  	}
   441  	found, reactionMsgID := supersededMsg.Reactions.HasReactionFromUser(msg.MessageBody.Reaction().Body,
   442  		s.G().Env.GetUsername().String())
   443  	if found {
   444  		msg.ClientHeader.Supersedes = reactionMsgID
   445  		msg.ClientHeader.MessageType = chat1.MessageType_DELETE
   446  		msg.ClientHeader.Deletes = []chat1.MessageID{reactionMsgID}
   447  		msg.MessageBody = chat1.NewMessageBodyWithDelete(chat1.MessageDelete{
   448  			MessageIDs: []chat1.MessageID{reactionMsgID},
   449  		})
   450  	} else {
   451  		// bookkeep the reaction used so we can keep track of the user's
   452  		// popular reactions in the UI
   453  		if err := storage.NewReacjiStore(s.G()).PutReacji(ctx, uid, msg.MessageBody.Reaction().Body); err != nil {
   454  			s.Debug(ctx, "unable to put in ReacjiStore: %v", err)
   455  		}
   456  		// set an @ mention on the message body for the author of the message we are reacting to
   457  		s.Debug(ctx, "processReactionMessage: adding target: %s", supersededMsg.ClientHeader.Sender)
   458  		body := msg.MessageBody.Reaction().DeepCopy()
   459  		body.TargetUID = &supersededMsg.ClientHeader.Sender
   460  		msg.MessageBody = chat1.NewMessageBodyWithReaction(body)
   461  	}
   462  
   463  	return msg.ClientHeader, msg.MessageBody, nil
   464  }
   465  
   466  func (s *BlockingSender) checkTopicNameAndGetState(ctx context.Context, msg chat1.MessagePlaintext,
   467  	membersType chat1.ConversationMembersType) (topicNameState *chat1.TopicNameState, convIDs []chat1.ConversationID, err error) {
   468  	if msg.ClientHeader.MessageType != chat1.MessageType_METADATA {
   469  		return topicNameState, convIDs, nil
   470  	}
   471  	tlfID := msg.ClientHeader.Conv.Tlfid
   472  	topicType := msg.ClientHeader.Conv.TopicType
   473  	switch topicType {
   474  	case chat1.TopicType_EMOJICROSS:
   475  		// skip this for this topic type
   476  		return topicNameState, convIDs, nil
   477  	default:
   478  	}
   479  	newTopicName := msg.MessageBody.Metadata().ConversationTitle
   480  	convs, err := s.G().TeamChannelSource.GetChannelsFull(ctx, msg.ClientHeader.Sender, tlfID, topicType)
   481  	if err != nil {
   482  		return nil, nil, err
   483  	}
   484  	var validConvs []chat1.ConversationLocal
   485  	for _, conv := range convs {
   486  		// If we have a conv error consider the conv invalid. Exclude
   487  		// the conv from out TopicNameState forcing the client to retry.
   488  		if conv.Error == nil {
   489  			if conv.GetTopicName() == "" {
   490  				s.Debug(ctx, "checkTopicNameAndGetState: unnamed channel in play: %s", conv.GetConvID())
   491  			}
   492  			validConvs = append(validConvs, conv)
   493  			convIDs = append(convIDs, conv.GetConvID())
   494  		} else {
   495  			s.Debug(ctx, "checkTopicNameAndGetState: skipping conv: %s, will cause an error from server",
   496  				conv.GetConvID())
   497  		}
   498  		if conv.GetTopicName() == newTopicName {
   499  			return nil, nil, DuplicateTopicNameError{Conv: conv}
   500  		}
   501  	}
   502  
   503  	ts, err := GetTopicNameState(ctx, s.G(), s.DebugLabeler, validConvs,
   504  		msg.ClientHeader.Sender, tlfID, topicType, membersType)
   505  	if err != nil {
   506  		return nil, nil, err
   507  	}
   508  	topicNameState = &ts
   509  	return topicNameState, convIDs, nil
   510  }
   511  
   512  func (s *BlockingSender) resolveOutboxIDEdit(ctx context.Context, uid gregor1.UID,
   513  	convID chat1.ConversationID, msg *chat1.MessagePlaintext) error {
   514  	if msg.SupersedesOutboxID == nil {
   515  		return nil
   516  	}
   517  	s.Debug(ctx, "resolveOutboxIDEdit: resolving edit: outboxID: %s", msg.SupersedesOutboxID)
   518  	typ, err := msg.MessageBody.MessageType()
   519  	if err != nil {
   520  		return err
   521  	}
   522  	if typ != chat1.MessageType_EDIT {
   523  		return errors.New("supersedes outboxID only valid for edit messages")
   524  	}
   525  	body := msg.MessageBody.Edit()
   526  	// try to find the message with the given outbox ID in the first 50 messages.
   527  	tv, err := s.G().ConvSource.Pull(ctx, convID, uid, chat1.GetThreadReason_PREPARE, nil,
   528  		&chat1.GetThreadQuery{
   529  			MessageTypes:             []chat1.MessageType{chat1.MessageType_TEXT},
   530  			DisableResolveSupersedes: true,
   531  		}, &chat1.Pagination{Num: 50})
   532  	if err != nil {
   533  		return err
   534  	}
   535  	for _, m := range tv.Messages {
   536  		if msg.SupersedesOutboxID.Eq(m.GetOutboxID()) {
   537  			s.Debug(ctx, "resolveOutboxIDEdit: resolved edit: outboxID: %s messageID: %v",
   538  				msg.SupersedesOutboxID, m.GetMessageID())
   539  			msg.ClientHeader.Supersedes = m.GetMessageID()
   540  			msg.MessageBody = chat1.NewMessageBodyWithEdit(chat1.MessageEdit{
   541  				MessageID: m.GetMessageID(),
   542  				Body:      body.Body,
   543  			})
   544  			return nil
   545  		}
   546  	}
   547  	return errors.New("failed to find message to edit")
   548  }
   549  
   550  func (s *BlockingSender) handleReplyTo(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
   551  	msg chat1.MessagePlaintext, replyTo *chat1.MessageID) (chat1.MessagePlaintext, error) {
   552  	if replyTo == nil {
   553  		return msg, nil
   554  	}
   555  	typ, err := msg.MessageBody.MessageType()
   556  	if err != nil {
   557  		s.Debug(ctx, "handleReplyTo: failed to get body type: %s", err)
   558  		return msg, nil
   559  	}
   560  	switch typ {
   561  	case chat1.MessageType_TEXT:
   562  		s.Debug(ctx, "handleReplyTo: handling text message")
   563  		header := msg.ClientHeader
   564  		header.Supersedes = *replyTo
   565  		reply, err := s.G().ChatHelper.GetMessage(ctx, uid, convID, *replyTo, false, nil)
   566  		if err != nil {
   567  			s.Debug(ctx, "handleReplyTo: failed to get reply message: %s", err)
   568  			return msg, err
   569  		}
   570  		if !reply.IsValid() {
   571  			s.Debug(ctx, "handleReplyTo: reply message invalid: %v %v", replyTo, err)
   572  			return msg, nil
   573  		}
   574  		replyToUID := reply.Valid().ClientHeader.Sender
   575  		newBody := msg.MessageBody.Text().DeepCopy()
   576  		newBody.ReplyTo = replyTo
   577  		newBody.ReplyToUID = &replyToUID
   578  		return chat1.MessagePlaintext{
   579  			ClientHeader:       header,
   580  			MessageBody:        chat1.NewMessageBodyWithText(newBody),
   581  			SupersedesOutboxID: msg.SupersedesOutboxID,
   582  		}, nil
   583  	default:
   584  		s.Debug(ctx, "handleReplyTo: skipping message of type: %v", typ)
   585  	}
   586  	return msg, nil
   587  }
   588  
   589  func (s *BlockingSender) handleEmojis(ctx context.Context, uid gregor1.UID,
   590  	convID chat1.ConversationID, msg chat1.MessagePlaintext, topicType chat1.TopicType) (chat1.MessagePlaintext, error) {
   591  	if topicType != chat1.TopicType_CHAT {
   592  		return msg, nil
   593  	}
   594  	typ, err := msg.MessageBody.MessageType()
   595  	if err != nil {
   596  		s.Debug(ctx, "handleEmojis: failed to get body type: %s", err)
   597  		return msg, nil
   598  	}
   599  	body := msg.MessageBody.TextForDecoration()
   600  	if len(body) == 0 {
   601  		return msg, nil
   602  	}
   603  	emojis, err := s.G().EmojiSource.Harvest(ctx, body, uid, convID, types.EmojiHarvestModeNormal)
   604  	if err != nil {
   605  		return msg, err
   606  	}
   607  	if len(emojis) == 0 {
   608  		return msg, nil
   609  	}
   610  	ct := make(map[string]chat1.HarvestedEmoji, len(emojis))
   611  	for _, emoji := range emojis {
   612  		ct[emoji.Alias] = emoji
   613  	}
   614  	s.Debug(ctx, "handleEmojis: found %d emojis", len(ct))
   615  	switch typ {
   616  	case chat1.MessageType_TEXT:
   617  		newBody := msg.MessageBody.Text().DeepCopy()
   618  		newBody.Emojis = ct
   619  		return chat1.MessagePlaintext{
   620  			ClientHeader:       msg.ClientHeader,
   621  			MessageBody:        chat1.NewMessageBodyWithText(newBody),
   622  			SupersedesOutboxID: msg.SupersedesOutboxID,
   623  		}, nil
   624  	case chat1.MessageType_REACTION:
   625  		newBody := msg.MessageBody.Reaction().DeepCopy()
   626  		newBody.Emojis = ct
   627  		return chat1.MessagePlaintext{
   628  			ClientHeader:       msg.ClientHeader,
   629  			MessageBody:        chat1.NewMessageBodyWithReaction(newBody),
   630  			SupersedesOutboxID: msg.SupersedesOutboxID,
   631  		}, nil
   632  	case chat1.MessageType_EDIT:
   633  		newBody := msg.MessageBody.Edit().DeepCopy()
   634  		newBody.Emojis = ct
   635  		return chat1.MessagePlaintext{
   636  			ClientHeader:       msg.ClientHeader,
   637  			MessageBody:        chat1.NewMessageBodyWithEdit(newBody),
   638  			SupersedesOutboxID: msg.SupersedesOutboxID,
   639  		}, nil
   640  	case chat1.MessageType_ATTACHMENT:
   641  		newBody := msg.MessageBody.Attachment().DeepCopy()
   642  		newBody.Emojis = ct
   643  		return chat1.MessagePlaintext{
   644  			ClientHeader:       msg.ClientHeader,
   645  			MessageBody:        chat1.NewMessageBodyWithAttachment(newBody),
   646  			SupersedesOutboxID: msg.SupersedesOutboxID,
   647  		}, nil
   648  	case chat1.MessageType_HEADLINE:
   649  		newBody := msg.MessageBody.Headline().DeepCopy()
   650  		newBody.Emojis = ct
   651  		return chat1.MessagePlaintext{
   652  			ClientHeader:       msg.ClientHeader,
   653  			MessageBody:        chat1.NewMessageBodyWithHeadline(newBody),
   654  			SupersedesOutboxID: msg.SupersedesOutboxID,
   655  		}, nil
   656  	}
   657  	return msg, nil
   658  }
   659  
   660  func (s *BlockingSender) getUsernamesForMentions(ctx context.Context, uid gregor1.UID,
   661  	conv *chat1.ConversationLocal) (res []string, err error) {
   662  	if conv == nil {
   663  		return nil, nil
   664  	}
   665  	defer s.Trace(ctx, &err, "getParticipantsForMentions")()
   666  	// get the conv that we will look for @ mentions in
   667  	switch conv.GetMembersType() {
   668  	case chat1.ConversationMembersType_TEAM:
   669  		teamID, err := keybase1.TeamIDFromString(conv.Info.Triple.Tlfid.String())
   670  		if err != nil {
   671  			return res, err
   672  		}
   673  		team, err := teams.Load(ctx, s.G().ExternalG(), keybase1.LoadTeamArg{
   674  			ID: teamID,
   675  		})
   676  		if err != nil {
   677  			return res, err
   678  		}
   679  		members, err := teams.MembersDetails(ctx, s.G().ExternalG(), team)
   680  		if err != nil {
   681  			return res, err
   682  		}
   683  		for _, memb := range members {
   684  			res = append(res, memb.Username)
   685  		}
   686  		return res, nil
   687  	default:
   688  		res = make([]string, 0, len(conv.Info.Participants))
   689  		for _, p := range conv.Info.Participants {
   690  			res = append(res, p.Username)
   691  		}
   692  		return res, nil
   693  	}
   694  }
   695  func (s *BlockingSender) handleMentions(ctx context.Context, uid gregor1.UID, msg chat1.MessagePlaintext,
   696  	conv *chat1.ConversationLocal) (res chat1.MessagePlaintext, atMentions []gregor1.UID, chanMention chat1.ChannelMention, err error) {
   697  	if msg.ClientHeader.Conv.TopicType != chat1.TopicType_CHAT {
   698  		return msg, atMentions, chanMention, nil
   699  	}
   700  	// Function to check that the header and body types match.
   701  	// Call this before accessing the body.
   702  	// Do not call this for TLFNAME which has no body.
   703  	checkHeaderBodyTypeMatch := func() error {
   704  		bodyType, err := msg.MessageBody.MessageType()
   705  		if err != nil {
   706  			return err
   707  		}
   708  		if msg.ClientHeader.MessageType != bodyType {
   709  			return fmt.Errorf("cannot send message with mismatched header/body types: %v != %v",
   710  				msg.ClientHeader.MessageType, bodyType)
   711  		}
   712  		return nil
   713  	}
   714  	atFromKnown := func(knowns []chat1.KnownUserMention) (res []gregor1.UID) {
   715  		for _, known := range knowns {
   716  			res = append(res, known.Uid)
   717  		}
   718  		return res
   719  	}
   720  	maybeToTeam := func(maybeMentions []chat1.MaybeMention) (res []chat1.KnownTeamMention) {
   721  		for _, maybe := range maybeMentions {
   722  			if s.G().TeamMentionLoader.IsTeamMention(ctx, uid, maybe, nil) {
   723  				res = append(res, chat1.KnownTeamMention(maybe))
   724  			}
   725  		}
   726  		return res
   727  	}
   728  
   729  	// find @ mentions
   730  	getConvUsernames := func() ([]string, error) {
   731  		return s.getUsernamesForMentions(ctx, uid, conv)
   732  	}
   733  	var knownUserMentions []chat1.KnownUserMention
   734  	var maybeMentions []chat1.MaybeMention
   735  	switch msg.ClientHeader.MessageType {
   736  	case chat1.MessageType_TEXT:
   737  		if err = checkHeaderBodyTypeMatch(); err != nil {
   738  			return res, atMentions, chanMention, err
   739  		}
   740  		knownUserMentions, maybeMentions, chanMention = utils.GetTextAtMentionedItems(ctx, s.G(),
   741  			uid, conv.GetConvID(), msg.MessageBody.Text(), getConvUsernames, &s.DebugLabeler)
   742  		atMentions = atFromKnown(knownUserMentions)
   743  		newBody := msg.MessageBody.Text().DeepCopy()
   744  		newBody.TeamMentions = maybeToTeam(maybeMentions)
   745  		newBody.UserMentions = knownUserMentions
   746  		res = chat1.MessagePlaintext{
   747  			ClientHeader:       msg.ClientHeader,
   748  			MessageBody:        chat1.NewMessageBodyWithText(newBody),
   749  			SupersedesOutboxID: msg.SupersedesOutboxID,
   750  		}
   751  	case chat1.MessageType_ATTACHMENT:
   752  		if err = checkHeaderBodyTypeMatch(); err != nil {
   753  			return res, atMentions, chanMention, err
   754  		}
   755  		knownUserMentions, maybeMentions, chanMention = utils.ParseAtMentionedItems(ctx, s.G(),
   756  			msg.MessageBody.Attachment().GetTitle(), nil, getConvUsernames)
   757  		atMentions = atFromKnown(knownUserMentions)
   758  		newBody := msg.MessageBody.Attachment().DeepCopy()
   759  		newBody.TeamMentions = maybeToTeam(maybeMentions)
   760  		newBody.UserMentions = knownUserMentions
   761  		res = chat1.MessagePlaintext{
   762  			ClientHeader:       msg.ClientHeader,
   763  			MessageBody:        chat1.NewMessageBodyWithAttachment(newBody),
   764  			SupersedesOutboxID: msg.SupersedesOutboxID,
   765  		}
   766  	case chat1.MessageType_FLIP:
   767  		if err = checkHeaderBodyTypeMatch(); err != nil {
   768  			return res, atMentions, chanMention, err
   769  		}
   770  		knownUserMentions, maybeMentions, chanMention = utils.ParseAtMentionedItems(ctx, s.G(),
   771  			msg.MessageBody.Flip().Text, nil, getConvUsernames)
   772  		atMentions = atFromKnown(knownUserMentions)
   773  		newBody := msg.MessageBody.Flip().DeepCopy()
   774  		newBody.TeamMentions = maybeToTeam(maybeMentions)
   775  		newBody.UserMentions = knownUserMentions
   776  		res = chat1.MessagePlaintext{
   777  			ClientHeader:       msg.ClientHeader,
   778  			MessageBody:        chat1.NewMessageBodyWithFlip(newBody),
   779  			SupersedesOutboxID: msg.SupersedesOutboxID,
   780  		}
   781  	case chat1.MessageType_EDIT:
   782  		if err = checkHeaderBodyTypeMatch(); err != nil {
   783  			return res, atMentions, chanMention, err
   784  		}
   785  		knownUserMentions, maybeMentions, chanMention = utils.ParseAtMentionedItems(ctx, s.G(),
   786  			msg.MessageBody.Edit().Body, nil, getConvUsernames)
   787  		atMentions = atFromKnown(knownUserMentions)
   788  		newBody := msg.MessageBody.Edit().DeepCopy()
   789  		newBody.TeamMentions = maybeToTeam(maybeMentions)
   790  		newBody.UserMentions = knownUserMentions
   791  		res = chat1.MessagePlaintext{
   792  			ClientHeader:       msg.ClientHeader,
   793  			MessageBody:        chat1.NewMessageBodyWithEdit(newBody),
   794  			SupersedesOutboxID: msg.SupersedesOutboxID,
   795  		}
   796  	case chat1.MessageType_REACTION:
   797  		targetUID := msg.MessageBody.Reaction().TargetUID
   798  		if targetUID != nil {
   799  			atMentions = []gregor1.UID{*targetUID}
   800  		}
   801  		res = msg
   802  	case chat1.MessageType_SYSTEM:
   803  		if err = checkHeaderBodyTypeMatch(); err != nil {
   804  			return res, atMentions, chanMention, err
   805  		}
   806  		res = msg
   807  		atMentions, chanMention, _ = utils.SystemMessageMentions(ctx, s.G(), uid, msg.MessageBody.System())
   808  	default:
   809  		res = msg
   810  	}
   811  	return res, atMentions, chanMention, nil
   812  }
   813  
   814  // Prepare a message to be sent.
   815  // Returns (boxedMessage, pendingAssetDeletes, error)
   816  func (s *BlockingSender) Prepare(ctx context.Context, plaintext chat1.MessagePlaintext,
   817  	membersType chat1.ConversationMembersType, conv *chat1.ConversationLocal,
   818  	inopts *chat1.SenderPrepareOptions) (res types.SenderPrepareResult, err error) {
   819  
   820  	if plaintext.ClientHeader.MessageType == chat1.MessageType_NONE {
   821  		return res, fmt.Errorf("cannot send message without type")
   822  	}
   823  	// set default options unless some are given to us
   824  	var opts chat1.SenderPrepareOptions
   825  	if inopts != nil {
   826  		opts = *inopts
   827  	}
   828  
   829  	msg, uid, err := s.addSenderToMessage(plaintext)
   830  	if err != nil {
   831  		return res, err
   832  	}
   833  
   834  	// Make sure our delete message gets everything it should
   835  	var pendingAssetDeletes []chat1.Asset
   836  	var deleteFlipConvID *chat1.ConversationID
   837  	if conv != nil {
   838  		convID := conv.GetConvID()
   839  		msg.ClientHeader.Conv = conv.Info.Triple
   840  		if len(msg.ClientHeader.TlfName) == 0 {
   841  			msg.ClientHeader.TlfName = conv.Info.TlfName
   842  			msg.ClientHeader.TlfPublic = conv.Info.Visibility == keybase1.TLFVisibility_PUBLIC
   843  		}
   844  		s.Debug(ctx, "Prepare: performing convID based checks")
   845  
   846  		// Check for outboxID based edits
   847  		if err = s.resolveOutboxIDEdit(ctx, uid, convID, &msg); err != nil {
   848  			s.Debug(ctx, "Prepare: error resolving outboxID edit: %s", err)
   849  			return res, err
   850  		}
   851  
   852  		// Add and check prev pointers
   853  		msg, err = s.addPrevPointersAndCheckConvID(ctx, msg, *conv)
   854  		if err != nil {
   855  			s.Debug(ctx, "Prepare: error adding prev pointers: %s", err)
   856  			return res, err
   857  		}
   858  
   859  		// First process the reactionMessage in case we convert it to a delete
   860  		header, body, err := s.processReactionMessage(ctx, uid, convID, msg)
   861  		if err != nil {
   862  			s.Debug(ctx, "Prepare: error processing reactions: %s", err)
   863  			return res, err
   864  		}
   865  		msg.ClientHeader = header
   866  		msg.MessageBody = body
   867  
   868  		// Handle reply to
   869  		if msg, err = s.handleReplyTo(ctx, uid, convID, msg, opts.ReplyTo); err != nil {
   870  			s.Debug(ctx, "Prepare: error processing reply: %s", err)
   871  			return res, err
   872  		}
   873  
   874  		// Handle cross team emoji
   875  		if msg, err = s.handleEmojis(ctx, uid, convID, msg, conv.GetTopicType()); err != nil {
   876  			s.Debug(ctx, "Prepare: error processing cross team emoji: %s", err)
   877  			return res, err
   878  		}
   879  
   880  		// Be careful not to shadow (msg, pendingAssetDeletes, deleteFlipConvID) with this assignment.
   881  		msg, pendingAssetDeletes, deleteFlipConvID, err = s.getAllDeletedEdits(ctx, uid, convID, msg)
   882  		if err != nil {
   883  			s.Debug(ctx, "Prepare: error getting deleted edits: %s", err)
   884  			return res, err
   885  		}
   886  
   887  		if !conv.GetTopicType().EphemeralAllowed() {
   888  			if msg.EphemeralMetadata() != nil {
   889  				return res, fmt.Errorf("%v messages cannot be ephemeral", conv.GetTopicType())
   890  			}
   891  		} else {
   892  			// If no ephemeral data set, then let's double check to make sure
   893  			// no exploding policy or Gregor state should set it if it's required.
   894  			if msg.EphemeralMetadata() == nil &&
   895  				chat1.IsEphemeralNonSupersederType(msg.ClientHeader.MessageType) &&
   896  				conv.GetTopicType().EphemeralRequired() {
   897  				s.Debug(ctx, "Prepare: attempting to set ephemeral policy from conversation")
   898  				elf, err := utils.EphemeralLifetimeFromConv(ctx, s.G(), *conv)
   899  				if err != nil {
   900  					s.Debug(ctx, "Prepare: failed to get ephemeral lifetime from conv: %s", err)
   901  					elf = nil
   902  				}
   903  				if elf != nil {
   904  					s.Debug(ctx, "Prepare: setting ephemeral lifetime from conv: %v", *elf)
   905  					msg.ClientHeader.EphemeralMetadata = &chat1.MsgEphemeralMetadata{
   906  						Lifetime: *elf,
   907  					}
   908  				}
   909  			}
   910  
   911  			msg.ClientHeader.EphemeralMetadata, err = s.getSupersederEphemeralMetadata(ctx, uid, convID, msg)
   912  			if err != nil {
   913  				s.Debug(ctx, "Prepare: error getting superseder ephemeral metadata: %s", err)
   914  				return res, err
   915  			}
   916  		}
   917  	}
   918  
   919  	// Make sure it is a proper length
   920  	if err := msgchecker.CheckMessagePlaintext(msg); err != nil {
   921  		s.Debug(ctx, "Prepare: error checking message plaintext: %s", err)
   922  		return res, err
   923  	}
   924  
   925  	// Get topic name state if this is a METADATA message, so that we avoid any races to the
   926  	// server
   927  	var topicNameState *chat1.TopicNameState
   928  	var topicNameStateConvs []chat1.ConversationID
   929  	if !opts.SkipTopicNameState {
   930  		if topicNameState, topicNameStateConvs, err = s.checkTopicNameAndGetState(ctx, msg, membersType); err != nil {
   931  			s.Debug(ctx, "Prepare: error checking topic name state: %s", err)
   932  			return res, err
   933  		}
   934  	}
   935  
   936  	// handle mentions
   937  	var atMentions []gregor1.UID
   938  	var chanMention chat1.ChannelMention
   939  	if msg, atMentions, chanMention, err = s.handleMentions(ctx, uid, msg, conv); err != nil {
   940  		s.Debug(ctx, "Prepare: error handling mentions: %s", err)
   941  		return res, err
   942  	}
   943  
   944  	// encrypt the message
   945  	skp, err := s.getSigningKeyPair(ctx)
   946  	if err != nil {
   947  		s.Debug(ctx, "Prepare: error getting signing key pair: %s", err)
   948  		return res, err
   949  	}
   950  
   951  	// If we are sending a message, and we think the conversation is a KBFS conversation, then set a label
   952  	// on the client header in case this conversation gets upgraded to impteam.
   953  	msg.ClientHeader.KbfsCryptKeysUsed = new(bool)
   954  	if membersType == chat1.ConversationMembersType_KBFS {
   955  		s.Debug(ctx, "setting KBFS crypt keys used flag")
   956  		*msg.ClientHeader.KbfsCryptKeysUsed = true
   957  	} else {
   958  		*msg.ClientHeader.KbfsCryptKeysUsed = false
   959  	}
   960  
   961  	var convID *chat1.ConversationID
   962  	if conv != nil {
   963  		id := conv.GetConvID()
   964  		convID = &id
   965  	}
   966  	botUIDs, err := s.applyTeamBotSettings(ctx, uid, &msg, convID, membersType, atMentions, opts)
   967  	if err != nil {
   968  		s.Debug(ctx, "Prepare: failed to apply team bot settings: %s", err)
   969  		return res, err
   970  	}
   971  	if len(botUIDs) > 0 {
   972  		// TODO HOTPOT-330 Add support for "hidden" messages for multiple bots
   973  		msg.ClientHeader.BotUID = &botUIDs[0]
   974  	}
   975  	s.Debug(ctx, "applyTeamBotSettings: matched %d bots, applied %v", len(botUIDs), msg.ClientHeader.BotUID)
   976  
   977  	encInfo, err := s.boxer.GetEncryptionInfo(ctx, &msg, membersType, skp)
   978  	if err != nil {
   979  		s.Debug(ctx, "Prepare: error getting encryption info: %s", err)
   980  		return res, err
   981  	}
   982  	boxed, err := s.boxer.BoxMessage(ctx, msg, membersType, skp, &encInfo)
   983  	if err != nil {
   984  		s.Debug(ctx, "Prepare: error boxing message: %s", err)
   985  		return res, err
   986  	}
   987  	return types.SenderPrepareResult{
   988  		Boxed:               boxed,
   989  		EncryptionInfo:      encInfo,
   990  		PendingAssetDeletes: pendingAssetDeletes,
   991  		DeleteFlipConv:      deleteFlipConvID,
   992  		AtMentions:          atMentions,
   993  		ChannelMention:      chanMention,
   994  		TopicNameState:      topicNameState,
   995  		TopicNameStateConvs: topicNameStateConvs,
   996  	}, nil
   997  }
   998  
   999  func (s *BlockingSender) applyTeamBotSettings(ctx context.Context, uid gregor1.UID,
  1000  	msg *chat1.MessagePlaintext, convID *chat1.ConversationID, membersType chat1.ConversationMembersType,
  1001  	atMentions []gregor1.UID, opts chat1.SenderPrepareOptions) ([]gregor1.UID, error) {
  1002  	// no bots in KBFS convs
  1003  	if membersType == chat1.ConversationMembersType_KBFS {
  1004  		return nil, nil
  1005  	}
  1006  
  1007  	// Skip checks if botUID already set
  1008  	if msg.ClientHeader.BotUID != nil {
  1009  		s.Debug(ctx, "applyTeamBotSettings: found existing botUID %v", msg.ClientHeader.BotUID)
  1010  		// verify this value is actually a restricted bot of the team.
  1011  		teamBotSettings, err := CreateNameInfoSource(ctx, s.G(), membersType).TeamBotSettings(ctx,
  1012  			msg.ClientHeader.TlfName, msg.ClientHeader.Conv.Tlfid, membersType, msg.ClientHeader.TlfPublic)
  1013  		if err != nil {
  1014  			return nil, err
  1015  		}
  1016  		for uv := range teamBotSettings {
  1017  			botUID := gregor1.UID(uv.Uid.ToBytes())
  1018  			if botUID.Eq(*msg.ClientHeader.BotUID) {
  1019  				s.Debug(ctx, "applyTeamBotSettings: existing botUID matches, short circuiting.")
  1020  				return nil, nil
  1021  			}
  1022  		}
  1023  		s.Debug(ctx, "applyTeamBotSettings: existing botUID %v does not match any bot, clearing")
  1024  		// Caller was mistaken, this uid is not actually a bot so we unset the
  1025  		// value.
  1026  		msg.ClientHeader.BotUID = nil
  1027  	}
  1028  
  1029  	// Check if we are superseding a bot message. If so, just take what the
  1030  	// superseded has. Don't automatically key for replies, run the normal checks.
  1031  	if msg.ClientHeader.Supersedes > 0 && opts.ReplyTo == nil && convID != nil {
  1032  		target, err := s.getMessage(ctx, uid, *convID, msg.ClientHeader.Supersedes, false /*resolveSupersedes */)
  1033  		if err != nil {
  1034  			return nil, err
  1035  		}
  1036  		botUID := target.ClientHeader.BotUID
  1037  		if botUID == nil {
  1038  			s.Debug(ctx, "applyTeamBotSettings: skipping, supersedes has nil botUID from msgID %d", msg.ClientHeader.Supersedes)
  1039  			return nil, nil
  1040  		}
  1041  		s.Debug(ctx, "applyTeamBotSettings: supersedes botUID %v from msgID %d", botUID, msg.ClientHeader.Supersedes)
  1042  		return []gregor1.UID{*botUID}, nil
  1043  	}
  1044  
  1045  	// Fetch the bot settings, if any
  1046  	teamBotSettings, err := CreateNameInfoSource(ctx, s.G(), membersType).TeamBotSettings(ctx,
  1047  		msg.ClientHeader.TlfName, msg.ClientHeader.Conv.Tlfid, membersType, msg.ClientHeader.TlfPublic)
  1048  	if err != nil {
  1049  		return nil, err
  1050  	}
  1051  
  1052  	mentionMap := make(map[string]struct{})
  1053  	for _, uid := range atMentions {
  1054  		mentionMap[uid.String()] = struct{}{}
  1055  	}
  1056  
  1057  	var botUIDs []gregor1.UID
  1058  	for uv, botSettings := range teamBotSettings {
  1059  		botUID := gregor1.UID(uv.Uid.ToBytes())
  1060  
  1061  		// If the bot is the sender encrypt only for them.
  1062  		if msg.ClientHeader.Sender.Eq(botUID) {
  1063  			if convID == nil || botSettings.ConvIDAllowed(convID.String()) {
  1064  				return []gregor1.UID{botUID}, nil
  1065  			}
  1066  			// Bot channel restrictions only apply to CHAT types.
  1067  			conv, err := utils.GetVerifiedConv(ctx, s.G(), uid, *convID, types.InboxSourceDataSourceAll)
  1068  			if err == nil && conv.GetTopicType() != chat1.TopicType_CHAT {
  1069  				return []gregor1.UID{botUID}, nil
  1070  			}
  1071  			return nil, NewRestrictedBotChannelError()
  1072  		}
  1073  
  1074  		isMatch, err := bots.ApplyTeamBotSettings(ctx, s.G(), botUID, botSettings, *msg,
  1075  			convID, mentionMap, s.DebugLabeler)
  1076  		if err != nil {
  1077  			return nil, err
  1078  		}
  1079  		s.Debug(ctx, "applyTeamBotSettings: applied settings for %+v for botuid: %v, senderUID: %v, convID: %v isMatch: %v",
  1080  			botSettings, uv.Uid, msg.ClientHeader.Sender, convID, isMatch)
  1081  		if isMatch {
  1082  			botUIDs = append(botUIDs, botUID)
  1083  		}
  1084  	}
  1085  	return botUIDs, nil
  1086  }
  1087  
  1088  func (s *BlockingSender) getSigningKeyPair(ctx context.Context) (kp libkb.NaclSigningKeyPair, err error) {
  1089  	// get device signing key for this user
  1090  	signingKey, err := engine.GetMySecretKey(ctx, s.G().ExternalG(),
  1091  		libkb.DeviceSigningKeyType, "sign chat message")
  1092  	if err != nil {
  1093  		return libkb.NaclSigningKeyPair{}, err
  1094  	}
  1095  	kp, ok := signingKey.(libkb.NaclSigningKeyPair)
  1096  	if !ok || kp.Private == nil {
  1097  		return libkb.NaclSigningKeyPair{}, libkb.KeyCannotSignError{}
  1098  	}
  1099  
  1100  	return kp, nil
  1101  }
  1102  
  1103  // deleteAssets deletes assets from s3.
  1104  // Logs but does not return errors. Assets may be left undeleted.
  1105  func (s *BlockingSender) deleteAssets(ctx context.Context, convID chat1.ConversationID, assets []chat1.Asset) error {
  1106  	// get s3 params from server
  1107  	params, err := s.getRi().GetS3Params(ctx, chat1.GetS3ParamsArg{
  1108  		ConversationID: convID,
  1109  		TempCreds:      true,
  1110  	})
  1111  	if err != nil {
  1112  		s.G().Log.Warning("error getting s3 params: %s", err)
  1113  		return nil
  1114  	}
  1115  
  1116  	if err := s.store.DeleteAssets(ctx, params, s, assets); err != nil {
  1117  		s.G().Log.Warning("error deleting assets: %s", err)
  1118  
  1119  		// there's no way to get asset information after this point.
  1120  		// any assets not deleted will be stranded on s3.
  1121  
  1122  		return nil
  1123  	}
  1124  
  1125  	s.G().Log.Debug("deleted %d assets", len(assets))
  1126  	return nil
  1127  }
  1128  
  1129  // Sign implements github.com/keybase/go/chat/s3.Signer interface.
  1130  func (s *BlockingSender) Sign(payload []byte) ([]byte, error) {
  1131  	arg := chat1.S3SignArg{
  1132  		Payload:   payload,
  1133  		Version:   1,
  1134  		TempCreds: true,
  1135  	}
  1136  	return s.getRi().S3Sign(context.Background(), arg)
  1137  }
  1138  
  1139  func (s *BlockingSender) presentUIItem(ctx context.Context, uid gregor1.UID, conv *chat1.ConversationLocal) (res *chat1.InboxUIItem) {
  1140  	if conv != nil {
  1141  		pc := utils.PresentConversationLocal(ctx, s.G(), uid, *conv, utils.PresentParticipantsModeSkip)
  1142  		res = &pc
  1143  	}
  1144  	return res
  1145  }
  1146  
  1147  func (s *BlockingSender) Send(ctx context.Context, convID chat1.ConversationID,
  1148  	msg chat1.MessagePlaintext, clientPrev chat1.MessageID,
  1149  	outboxID *chat1.OutboxID, sendOpts *chat1.SenderSendOptions, prepareOpts *chat1.SenderPrepareOptions) (obid chat1.OutboxID, boxed *chat1.MessageBoxed, err error) {
  1150  	defer s.Trace(ctx, &err, fmt.Sprintf("Send(%s)", convID))()
  1151  	defer utils.SuspendComponent(ctx, s.G(), s.G().InboxSource)()
  1152  
  1153  	// Get conversation metadata first. If we can't find it, we will just attempt to join
  1154  	// the conversation in case that is an option. If it succeeds, then we just keep going,
  1155  	// otherwise we give up and return an error.
  1156  	var conv chat1.ConversationLocal
  1157  	sender := gregor1.UID(s.G().Env.GetUID().ToBytes())
  1158  	conv, err = utils.GetVerifiedConv(ctx, s.G(), sender, convID, types.InboxSourceDataSourceAll)
  1159  	if err != nil {
  1160  		s.Debug(ctx, "Send: error getting conversation metadata: %v", err)
  1161  		return nil, nil, err
  1162  	}
  1163  	s.Debug(ctx, "Send: uid: %s in conversation %s (tlfName: %s) with status: %v", sender,
  1164  		conv.GetConvID(), conv.Info.TlfName, conv.ReaderInfo.Status)
  1165  
  1166  	// If we are in preview mode, then just join the conversation right now.
  1167  	switch conv.ReaderInfo.Status {
  1168  	case chat1.ConversationMemberStatus_PREVIEW, chat1.ConversationMemberStatus_NEVER_JOINED:
  1169  		switch msg.ClientHeader.MessageType {
  1170  		case chat1.MessageType_JOIN,
  1171  			chat1.MessageType_LEAVE,
  1172  			chat1.MessageType_HEADLINE,
  1173  			chat1.MessageType_METADATA,
  1174  			chat1.MessageType_SYSTEM: // don't need to join to send a system message.
  1175  			// pass so we don't loop between Send and Join/Leave or join when
  1176  			// updating the metadata/headline.
  1177  		default:
  1178  			s.Debug(ctx, "Send: user is in mode: %v, joining conversation", conv.ReaderInfo.Status)
  1179  			if err = JoinConversation(ctx, s.G(), s.DebugLabeler, s.getRi, sender, convID); err != nil {
  1180  				return nil, nil, err
  1181  			}
  1182  		}
  1183  	default:
  1184  		// do nothing
  1185  	}
  1186  
  1187  	var prepareRes types.SenderPrepareResult
  1188  	var plres chat1.PostRemoteRes
  1189  	// If we get a ChatStalePreviousStateError we blow away in the box cache
  1190  	// once to allow the retry to get fresh data.
  1191  	clearedCache := false
  1192  	// Try this up to 5 times in case we are trying to set the topic name, and the topic name
  1193  	// state is moving around underneath us.
  1194  	for i := 0; i < 5; i++ {
  1195  		// Add a bunch of stuff to the message (like prev pointers, sender info, ...)
  1196  		if prepareRes, err = s.Prepare(ctx, msg, conv.GetMembersType(), &conv, prepareOpts); err != nil {
  1197  			s.Debug(ctx, "Send: error in Prepare: %s", err)
  1198  			return nil, nil, err
  1199  		}
  1200  		boxed = &prepareRes.Boxed
  1201  
  1202  		// Log some useful information about the message we are sending
  1203  		obidstr := "(none)"
  1204  		if boxed.ClientHeader.OutboxID != nil {
  1205  			obidstr = boxed.ClientHeader.OutboxID.String()
  1206  		}
  1207  		s.Debug(ctx, "Send: sending message: convID: %s outboxID: %s", convID, obidstr)
  1208  
  1209  		// Keep trying if we get an error on topicNameState for a fixed number of times
  1210  		rarg := chat1.PostRemoteArg{
  1211  			ConversationID: convID,
  1212  			MessageBoxed:   *boxed,
  1213  			AtMentions:     prepareRes.AtMentions,
  1214  			ChannelMention: prepareRes.ChannelMention,
  1215  			TopicNameState: prepareRes.TopicNameState,
  1216  			JoinMentionsAs: sendOpts.GetJoinMentionsAs(),
  1217  		}
  1218  		plres, err = s.getRi().PostRemote(ctx, rarg)
  1219  		if err != nil {
  1220  			switch e := err.(type) {
  1221  			case libkb.ChatStalePreviousStateError:
  1222  				// If we hit the stale previous state error, that means we should try again, since our view is
  1223  				// out of date.
  1224  				s.Debug(ctx, "Send: failed because of stale previous state, trying the whole thing again")
  1225  				if !clearedCache {
  1226  					s.Debug(ctx, "Send: clearing inbox cache to retry stale previous state")
  1227  					if err := s.G().InboxSource.Clear(ctx, sender, &types.ClearOpts{
  1228  						SendLocalAdminNotification: true,
  1229  						Reason:                     "stale previous topic state",
  1230  					}); err != nil {
  1231  						s.Debug(ctx, "Send: error clearing: %+v", err)
  1232  					}
  1233  					s.Debug(ctx, "Send: clearing conversation cache to retry stale previous state: %d convs",
  1234  						len(prepareRes.TopicNameStateConvs))
  1235  					for _, convID := range prepareRes.TopicNameStateConvs {
  1236  						if err := s.G().ConvSource.Clear(ctx, convID, sender, nil); err != nil {
  1237  							s.Debug(ctx, "Send: error clearing: %v %+v", convID, err)
  1238  						}
  1239  					}
  1240  					clearedCache = true
  1241  				}
  1242  				continue
  1243  			case libkb.ChatEphemeralRetentionPolicyViolatedError:
  1244  				s.Debug(ctx, "Send: failed because of invalid ephemeral policy, trying the whole thing again")
  1245  				var cerr error
  1246  				conv, cerr = utils.GetVerifiedConv(ctx, s.G(), sender, convID,
  1247  					types.InboxSourceDataSourceRemoteOnly)
  1248  				if cerr != nil {
  1249  					return nil, nil, cerr
  1250  				}
  1251  				continue
  1252  			case libkb.EphemeralPairwiseMACsMissingUIDsError:
  1253  				s.Debug(ctx, "Send: failed because of missing KIDs for pairwise MACs, reloading UPAKs for %v and retrying.", e.UIDs)
  1254  				err := utils.ForceReloadUPAKsForUIDs(ctx, s.G(), e.UIDs)
  1255  				if err != nil {
  1256  					s.Debug(ctx, "Send: error forcing reloads: %+v", err)
  1257  				}
  1258  				continue
  1259  			default:
  1260  				s.Debug(ctx, "Send: failed to PostRemote, bailing: %s", err)
  1261  				return nil, nil, err
  1262  			}
  1263  		}
  1264  		boxed.ServerHeader = &plres.MsgHeader
  1265  
  1266  		// Delete assets associated with a delete operation.
  1267  		// Logs instead of returning an error. Assets can be left undeleted.
  1268  		if len(prepareRes.PendingAssetDeletes) > 0 {
  1269  			err = s.deleteAssets(ctx, convID, prepareRes.PendingAssetDeletes)
  1270  			if err != nil {
  1271  				s.Debug(ctx, "Send: failure in deleteAssets: %s", err)
  1272  			}
  1273  		}
  1274  
  1275  		if prepareRes.DeleteFlipConv != nil {
  1276  			_, err = s.getRi().DeleteConversation(ctx, *prepareRes.DeleteFlipConv)
  1277  			if err != nil {
  1278  				s.Debug(ctx, "Send: failure in DeleteConversation: %s", err)
  1279  			}
  1280  		}
  1281  		break
  1282  	}
  1283  	if err != nil {
  1284  		return nil, nil, err
  1285  	}
  1286  
  1287  	// If this message was sent from the Outbox, then we can remove it now
  1288  	if boxed.ClientHeader.OutboxID != nil {
  1289  		if _, err = storage.NewOutbox(s.G(), sender).RemoveMessage(ctx, *boxed.ClientHeader.OutboxID); err != nil {
  1290  			s.Debug(ctx, "unable to remove outbox message: %v", err)
  1291  		}
  1292  	}
  1293  
  1294  	// Write new message out to cache and other followup
  1295  	var cerr error
  1296  	var convLocal *chat1.ConversationLocal
  1297  	s.Debug(ctx, "sending local updates to chat sources")
  1298  	// unbox using encryption info we already have
  1299  	unboxedMsg, err := s.boxer.UnboxMessage(ctx, *boxed, conv, &prepareRes.EncryptionInfo)
  1300  	if err != nil {
  1301  		s.Debug(ctx, "Send: failed to unbox sent message: %s", err)
  1302  	} else {
  1303  		if cerr = s.G().ConvSource.PushUnboxed(ctx, conv, boxed.ClientHeader.Sender,
  1304  			[]chat1.MessageUnboxed{unboxedMsg}); cerr != nil {
  1305  			s.Debug(ctx, "Send: failed to push new message into convsource: %s", err)
  1306  		}
  1307  	}
  1308  	if convLocal, err = s.G().InboxSource.NewMessage(ctx, boxed.ClientHeader.Sender, 0, convID,
  1309  		*boxed, nil); err != nil {
  1310  		s.Debug(ctx, "Send: failed to update inbox: %s", err)
  1311  	}
  1312  	// Send up to frontend
  1313  	if cerr == nil && boxed.GetMessageType() != chat1.MessageType_LEAVE {
  1314  		unboxedMsg, err = NewReplyFiller(s.G()).FillSingle(ctx, boxed.ClientHeader.Sender, convID,
  1315  			unboxedMsg)
  1316  		if err != nil {
  1317  			s.Debug(ctx, "Send: failed to fill reply: %s", err)
  1318  		}
  1319  		activity := chat1.NewChatActivityWithIncomingMessage(chat1.IncomingMessage{
  1320  			Message: utils.PresentMessageUnboxed(ctx, s.G(), unboxedMsg, boxed.ClientHeader.Sender,
  1321  				convID),
  1322  			ConvID:                     convID,
  1323  			DisplayDesktopNotification: false,
  1324  			Conv:                       s.presentUIItem(ctx, boxed.ClientHeader.Sender, convLocal),
  1325  		})
  1326  		s.G().ActivityNotifier.Activity(ctx, boxed.ClientHeader.Sender, conv.GetTopicType(), &activity,
  1327  			chat1.ChatActivitySource_LOCAL)
  1328  	}
  1329  	if conv.GetTopicType() == chat1.TopicType_CHAT {
  1330  		// Unfurl
  1331  		go s.G().Unfurler.UnfurlAndSend(globals.BackgroundChatCtx(ctx, s.G()), boxed.ClientHeader.Sender,
  1332  			convID, unboxedMsg)
  1333  		// Start tracking any live location sends
  1334  		if unboxedMsg.IsValid() && unboxedMsg.GetMessageType() == chat1.MessageType_TEXT &&
  1335  			unboxedMsg.Valid().MessageBody.Text().LiveLocation != nil {
  1336  			if unboxedMsg.Valid().MessageBody.Text().LiveLocation.EndTime.IsZero() {
  1337  				s.G().LiveLocationTracker.GetCurrentPosition(ctx, conv.GetConvID(),
  1338  					unboxedMsg.GetMessageID())
  1339  			} else {
  1340  				s.G().LiveLocationTracker.StartTracking(ctx, conv.GetConvID(), unboxedMsg.GetMessageID(),
  1341  					gregor1.FromTime(unboxedMsg.Valid().MessageBody.Text().LiveLocation.EndTime))
  1342  			}
  1343  		}
  1344  		if conv.GetMembersType() == chat1.ConversationMembersType_TEAM {
  1345  			teamID, err := keybase1.TeamIDFromString(conv.Info.Triple.Tlfid.String())
  1346  			if err != nil {
  1347  				s.Debug(ctx, "Send: failed to get team ID: %v", err)
  1348  			} else {
  1349  				go s.G().JourneyCardManager.SentMessage(globals.BackgroundChatCtx(ctx, s.G()), sender, teamID, convID)
  1350  			}
  1351  		}
  1352  	}
  1353  	return nil, boxed, nil
  1354  }
  1355  
  1356  type NonblockingSender struct {
  1357  	globals.Contextified
  1358  	utils.DebugLabeler
  1359  	sender types.Sender
  1360  }
  1361  
  1362  var _ types.Sender = (*NonblockingSender)(nil)
  1363  
  1364  func NewNonblockingSender(g *globals.Context, sender types.Sender) *NonblockingSender {
  1365  	s := &NonblockingSender{
  1366  		Contextified: globals.NewContextified(g),
  1367  		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "NonblockingSender", false),
  1368  		sender:       sender,
  1369  	}
  1370  	return s
  1371  }
  1372  
  1373  func (s *NonblockingSender) Prepare(ctx context.Context, msg chat1.MessagePlaintext,
  1374  	membersType chat1.ConversationMembersType, conv *chat1.ConversationLocal,
  1375  	opts *chat1.SenderPrepareOptions) (types.SenderPrepareResult, error) {
  1376  	return s.sender.Prepare(ctx, msg, membersType, conv, opts)
  1377  }
  1378  
  1379  func (s *NonblockingSender) Send(ctx context.Context, convID chat1.ConversationID,
  1380  	msg chat1.MessagePlaintext, clientPrev chat1.MessageID, outboxID *chat1.OutboxID,
  1381  	sendOpts *chat1.SenderSendOptions, prepareOpts *chat1.SenderPrepareOptions) (chat1.OutboxID, *chat1.MessageBoxed, error) {
  1382  	uid, err := utils.AssertLoggedInUID(ctx, s.G())
  1383  	if err != nil {
  1384  		return nil, nil, err
  1385  	}
  1386  	// The strategy here is to select the larger prev between what the UI provides, and what we have
  1387  	// stored locally. If we just use the UI version, then we can race for creating ordinals in
  1388  	// Outbox.PushMessage. However, in rare cases we might not have something locally, in that case just
  1389  	// fallback to the UI provided number.
  1390  	var storedPrev chat1.MessageID
  1391  	conv, err := utils.GetUnverifiedConv(ctx, s.G(), uid, convID, types.InboxSourceDataSourceLocalOnly)
  1392  	if err != nil {
  1393  		s.Debug(ctx, "Send: failed to get local inbox info: %s", err)
  1394  	} else {
  1395  		storedPrev = conv.Conv.GetMaxMessageID()
  1396  	}
  1397  	if storedPrev > clientPrev {
  1398  		clientPrev = storedPrev
  1399  	}
  1400  	if clientPrev == 0 {
  1401  		clientPrev = 1
  1402  	}
  1403  	s.Debug(ctx, "Send: using prevMsgID: %d", clientPrev)
  1404  	msg.ClientHeader.OutboxInfo = &chat1.OutboxInfo{
  1405  		Prev:        clientPrev,
  1406  		ComposeTime: gregor1.ToTime(time.Now()),
  1407  	}
  1408  	identifyBehavior, _, _ := globals.CtxIdentifyMode(ctx)
  1409  	obr, err := s.G().MessageDeliverer.Queue(ctx, convID, msg, outboxID, sendOpts, prepareOpts,
  1410  		identifyBehavior)
  1411  	if err != nil {
  1412  		return obr.OutboxID, nil, err
  1413  	}
  1414  	return obr.OutboxID, nil, nil
  1415  }
  1416  
  1417  func (s *NonblockingSender) SendUnfurlNonblock(ctx context.Context, convID chat1.ConversationID,
  1418  	msg chat1.MessagePlaintext, clientPrev chat1.MessageID, outboxID chat1.OutboxID) (chat1.OutboxID, error) {
  1419  	res, _, err := s.Send(ctx, convID, msg, clientPrev, &outboxID, nil, nil)
  1420  	return res, err
  1421  }