github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/protocol/chat1/extras.go (about)

     1  package chat1
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/hmac"
     6  	"crypto/sha256"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"hash"
    13  	"math"
    14  	"path/filepath"
    15  	"regexp"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  	"unicode/utf8"
    22  
    23  	"github.com/keybase/client/go/protocol/gregor1"
    24  	"github.com/keybase/client/go/protocol/keybase1"
    25  )
    26  
    27  // we will show some representation of an exploded message in the UI for a week
    28  const ShowExplosionLifetime = time.Hour * 24 * 7
    29  
    30  // If a conversation is larger, only admins can @channel.
    31  const MaxChanMentionConvSize = 100
    32  
    33  func (i FlipGameIDStr) String() string {
    34  	return string(i)
    35  }
    36  
    37  func (i TLFIDStr) String() string {
    38  	return string(i)
    39  }
    40  
    41  func (i ConvIDStr) String() string {
    42  	return string(i)
    43  }
    44  
    45  type ByUID []gregor1.UID
    46  type ConvIDShort = []byte
    47  
    48  func (b ByUID) Len() int      { return len(b) }
    49  func (b ByUID) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
    50  func (b ByUID) Less(i, j int) bool {
    51  	return bytes.Compare(b[i].Bytes(), b[j].Bytes()) < 0
    52  }
    53  
    54  // Eq compares two TLFIDs
    55  func (id TLFID) Eq(other TLFID) bool {
    56  	return bytes.Equal([]byte(id), []byte(other))
    57  }
    58  
    59  // EqString is like EqualsTo, except that it accepts a fmt.Stringer. This
    60  // can be useful for comparing keybase1.TLFID and chat1.TLFID.
    61  func (id TLFID) EqString(other fmt.Stringer) bool {
    62  	return hex.EncodeToString(id) == other.String()
    63  }
    64  
    65  func (id TLFID) String() string {
    66  	return hex.EncodeToString(id)
    67  }
    68  
    69  func (id TLFID) Bytes() []byte {
    70  	return []byte(id)
    71  }
    72  
    73  func (id TLFID) TLFIDStr() TLFIDStr {
    74  	return TLFIDStr(id.String())
    75  }
    76  
    77  func (id TLFID) IsNil() bool {
    78  	return len(id) == 0
    79  }
    80  
    81  func (id TLFID) IsTeamID() bool {
    82  	if len(id) != keybase1.TEAMID_LEN {
    83  		return false
    84  	}
    85  	switch id[len(id)-1] {
    86  	case keybase1.TEAMID_PRIVATE_SUFFIX,
    87  		keybase1.TEAMID_PUBLIC_SUFFIX,
    88  		keybase1.SUB_TEAMID_PRIVATE_SUFFIX,
    89  		keybase1.SUB_TEAMID_PUBLIC_SUFFIX:
    90  		return true
    91  	default:
    92  		return false
    93  	}
    94  }
    95  
    96  func MakeConvID(val string) (ConversationID, error) {
    97  	return hex.DecodeString(val)
    98  }
    99  
   100  func (cid ConversationID) String() string {
   101  	return hex.EncodeToString(cid)
   102  }
   103  
   104  func (cid ConversationID) Bytes() []byte {
   105  	return []byte(cid)
   106  }
   107  
   108  func (cid ConversationID) ConvIDStr() ConvIDStr {
   109  	return ConvIDStr(cid.String())
   110  }
   111  
   112  func (cid ConversationID) IsNil() bool {
   113  	return len(cid) < DbShortFormLen
   114  }
   115  
   116  func (cid ConversationID) Eq(c ConversationID) bool {
   117  	return bytes.Equal(cid, c)
   118  }
   119  
   120  func (cid ConversationID) Less(c ConversationID) bool {
   121  	return bytes.Compare(cid, c) < 0
   122  }
   123  
   124  const DbShortFormLen = 10
   125  
   126  // DbShortForm should only be used when interacting with the database, and should
   127  // never leave Gregor
   128  func (cid ConversationID) DbShortForm() ConvIDShort {
   129  	end := DbShortFormLen
   130  	if end > len(cid) {
   131  		end = len(cid)
   132  	}
   133  	return cid[:end]
   134  }
   135  
   136  func (cid ConversationID) DbShortFormString() string {
   137  	return DbShortFormToString(cid.DbShortForm())
   138  }
   139  
   140  func DbShortFormToString(cid ConvIDShort) string {
   141  	return hex.EncodeToString(cid)
   142  }
   143  
   144  func DbShortFormFromString(cid string) (ConvIDShort, error) {
   145  	return hex.DecodeString(cid)
   146  }
   147  
   148  func MakeTLFID(val string) (TLFID, error) {
   149  	return hex.DecodeString(val)
   150  }
   151  
   152  func MakeTopicID(val string) (TopicID, error) {
   153  	return hex.DecodeString(val)
   154  }
   155  
   156  func MakeTopicType(val int64) TopicType {
   157  	return TopicType(val)
   158  }
   159  
   160  func (mid MessageID) String() string {
   161  	return strconv.FormatUint(uint64(mid), 10)
   162  }
   163  
   164  func (mid MessageID) Min(mid2 MessageID) MessageID {
   165  	if mid < mid2 {
   166  		return mid
   167  	}
   168  	return mid2
   169  }
   170  
   171  func (mid MessageID) IsNil() bool {
   172  	return uint(mid) == 0
   173  }
   174  
   175  func (mid MessageID) Advance(num uint) MessageID {
   176  	return MessageID(uint(mid) + num)
   177  }
   178  
   179  func (t MessageType) String() string {
   180  	s, ok := MessageTypeRevMap[t]
   181  	if ok {
   182  		return s
   183  	}
   184  	return "UNKNOWN"
   185  }
   186  
   187  // Message types deletable by a standard DELETE message.
   188  var deletableMessageTypesByDelete = []MessageType{
   189  	MessageType_TEXT,
   190  	MessageType_ATTACHMENT,
   191  	MessageType_EDIT,
   192  	MessageType_ATTACHMENTUPLOADED,
   193  	MessageType_REACTION,
   194  	MessageType_REQUESTPAYMENT,
   195  	MessageType_UNFURL,
   196  	MessageType_PIN,
   197  	MessageType_HEADLINE,
   198  	MessageType_SYSTEM,
   199  	MessageType_FLIP,
   200  }
   201  
   202  // Messages types NOT deletable by a DELETEHISTORY message.
   203  var nonDeletableMessageTypesByDeleteHistory = []MessageType{
   204  	MessageType_NONE,
   205  	MessageType_DELETE,
   206  	MessageType_TLFNAME,
   207  	MessageType_DELETEHISTORY,
   208  }
   209  
   210  func DeletableMessageTypesByDelete() []MessageType {
   211  	return deletableMessageTypesByDelete
   212  }
   213  
   214  func IsSystemMsgDeletableByDelete(typ MessageSystemType) bool {
   215  	switch typ {
   216  	case MessageSystemType_ADDEDTOTEAM,
   217  		MessageSystemType_INVITEADDEDTOTEAM,
   218  		MessageSystemType_GITPUSH,
   219  		MessageSystemType_CHANGEAVATAR,
   220  		MessageSystemType_CHANGERETENTION,
   221  		MessageSystemType_BULKADDTOCONV,
   222  		MessageSystemType_SBSRESOLVE,
   223  		MessageSystemType_NEWCHANNEL:
   224  		return true
   225  	case MessageSystemType_COMPLEXTEAM,
   226  		MessageSystemType_CREATETEAM:
   227  		return false
   228  	default:
   229  		return false
   230  	}
   231  }
   232  
   233  var visibleMessageTypes = []MessageType{
   234  	MessageType_TEXT,
   235  	MessageType_ATTACHMENT,
   236  	MessageType_JOIN,
   237  	MessageType_LEAVE,
   238  	MessageType_SYSTEM,
   239  	MessageType_SENDPAYMENT,
   240  	MessageType_REQUESTPAYMENT,
   241  	MessageType_FLIP,
   242  	MessageType_HEADLINE,
   243  	MessageType_PIN,
   244  }
   245  
   246  // Visible chat messages appear visually as a message in the conv.
   247  // For counterexample REACTION and DELETE_HISTORY have visual effects but do not appear as a message.
   248  func VisibleChatMessageTypes() []MessageType {
   249  	return visibleMessageTypes
   250  }
   251  
   252  var badgeableMessageTypes = []MessageType{
   253  	MessageType_TEXT,
   254  	MessageType_ATTACHMENT,
   255  	MessageType_SYSTEM,
   256  	MessageType_SENDPAYMENT,
   257  	MessageType_REQUESTPAYMENT,
   258  	MessageType_FLIP,
   259  	MessageType_HEADLINE,
   260  	MessageType_PIN,
   261  }
   262  
   263  // Message types that cause badges.
   264  // JOIN and LEAVE are Visible but are too minute to badge.
   265  func BadgeableMessageTypes() []MessageType {
   266  	return badgeableMessageTypes
   267  }
   268  
   269  // A conversation is considered 'empty' unless it has one of these message types.
   270  // Used for filtering empty convs out of the the inbox.
   271  func NonEmptyConvMessageTypes() []MessageType {
   272  	return badgeableMessageTypes
   273  }
   274  
   275  var snippetMessageTypes = []MessageType{
   276  	MessageType_TEXT,
   277  	MessageType_ATTACHMENT,
   278  	MessageType_SYSTEM,
   279  	MessageType_DELETEHISTORY,
   280  	MessageType_SENDPAYMENT,
   281  	MessageType_REQUESTPAYMENT,
   282  	MessageType_FLIP,
   283  	MessageType_HEADLINE,
   284  	MessageType_PIN,
   285  }
   286  
   287  // Snippet chat messages can be the snippet of a conversation.
   288  func SnippetChatMessageTypes() []MessageType {
   289  	return snippetMessageTypes
   290  }
   291  
   292  var editableMessageTypesByEdit = []MessageType{
   293  	MessageType_TEXT,
   294  	MessageType_ATTACHMENT,
   295  }
   296  
   297  func EditableMessageTypesByEdit() []MessageType {
   298  	return editableMessageTypesByEdit
   299  }
   300  
   301  func IsEphemeralSupersederType(typ MessageType) bool {
   302  	switch typ {
   303  	case MessageType_EDIT,
   304  		MessageType_ATTACHMENTUPLOADED,
   305  		MessageType_REACTION,
   306  		MessageType_UNFURL:
   307  		return true
   308  	default:
   309  		return false
   310  	}
   311  }
   312  
   313  func IsEphemeralNonSupersederType(typ MessageType) bool {
   314  	switch typ {
   315  	case MessageType_TEXT,
   316  		MessageType_ATTACHMENT,
   317  		MessageType_FLIP:
   318  		return true
   319  	default:
   320  		return false
   321  	}
   322  }
   323  
   324  func IsEphemeralType(typ MessageType) bool {
   325  	return IsEphemeralNonSupersederType(typ) || IsEphemeralSupersederType(typ)
   326  }
   327  
   328  func DeletableMessageTypesByDeleteHistory() (res []MessageType) {
   329  	banned := make(map[MessageType]bool)
   330  	for _, mt := range nonDeletableMessageTypesByDeleteHistory {
   331  		banned[mt] = true
   332  	}
   333  	for _, mt := range MessageTypeMap {
   334  		if !banned[mt] {
   335  			res = append(res, mt)
   336  		}
   337  	}
   338  	sort.Slice(res, func(i, j int) bool {
   339  		return res[i] < res[j]
   340  	})
   341  	return res
   342  }
   343  
   344  func IsDeletableByDelete(typ MessageType) bool {
   345  	for _, typ2 := range deletableMessageTypesByDelete {
   346  		if typ == typ2 {
   347  			return true
   348  		}
   349  	}
   350  	return false
   351  }
   352  
   353  func IsDeletableByDeleteHistory(typ MessageType) bool {
   354  	for _, typ2 := range nonDeletableMessageTypesByDeleteHistory {
   355  		if typ == typ2 {
   356  			return false
   357  		}
   358  	}
   359  	return true
   360  }
   361  
   362  // EphemeralAllowed flags if the given topic type is allowed to send ephemeral
   363  // messages at all.
   364  func (t TopicType) EphemeralAllowed() bool {
   365  	switch t {
   366  	case TopicType_KBFSFILEEDIT,
   367  		TopicType_EMOJI,
   368  		TopicType_EMOJICROSS:
   369  		return false
   370  	default:
   371  		return true
   372  	}
   373  }
   374  
   375  // EphemeralRequired flags if the given topic type required to respect the
   376  // ephemeral retention policy if set.
   377  func (t TopicType) EphemeralRequired() bool {
   378  	switch t {
   379  	case TopicType_DEV:
   380  		return false
   381  	default:
   382  		return t.EphemeralAllowed()
   383  	}
   384  }
   385  
   386  func (t TopicType) String() string {
   387  	s, ok := TopicTypeRevMap[t]
   388  	if ok {
   389  		return s
   390  	}
   391  	return "UNKNOWN"
   392  }
   393  
   394  func (t TopicID) String() string {
   395  	return hex.EncodeToString(t)
   396  }
   397  
   398  func (t TopicID) Eq(r TopicID) bool {
   399  	return bytes.Equal([]byte(t), []byte(r))
   400  }
   401  
   402  func (t ConversationIDTriple) Eq(other ConversationIDTriple) bool {
   403  	return t.Tlfid.Eq(other.Tlfid) &&
   404  		bytes.Equal([]byte(t.TopicID), []byte(other.TopicID)) &&
   405  		t.TopicType == other.TopicType
   406  }
   407  
   408  func (hash Hash) String() string {
   409  	return hex.EncodeToString(hash)
   410  }
   411  
   412  func (hash Hash) Eq(other Hash) bool {
   413  	return bytes.Equal(hash, other)
   414  }
   415  
   416  func (m MessageUnboxed) SenderEq(o MessageUnboxed) bool {
   417  	if state, err := m.State(); err == nil {
   418  		if ostate, err := o.State(); err == nil && state == ostate {
   419  			switch state {
   420  			case MessageUnboxedState_VALID:
   421  				return m.Valid().SenderEq(o.Valid())
   422  			case MessageUnboxedState_ERROR:
   423  				return m.Error().SenderEq(o.Error())
   424  			case MessageUnboxedState_OUTBOX:
   425  				return m.Outbox().SenderEq(o.Outbox())
   426  			}
   427  		}
   428  	}
   429  	return false
   430  }
   431  
   432  func (m MessageUnboxed) OutboxID() *OutboxID {
   433  	if state, err := m.State(); err == nil {
   434  		switch state {
   435  		case MessageUnboxedState_VALID:
   436  			return m.Valid().ClientHeader.OutboxID
   437  		case MessageUnboxedState_ERROR:
   438  			return nil
   439  		case MessageUnboxedState_PLACEHOLDER:
   440  			return nil
   441  		case MessageUnboxedState_OUTBOX:
   442  			return m.Outbox().Msg.ClientHeader.OutboxID
   443  		default:
   444  			return nil
   445  		}
   446  	}
   447  	return nil
   448  }
   449  
   450  func (m MessageUnboxed) GetMessageID() MessageID {
   451  	if state, err := m.State(); err == nil {
   452  		switch state {
   453  		case MessageUnboxedState_VALID:
   454  			return m.Valid().ServerHeader.MessageID
   455  		case MessageUnboxedState_ERROR:
   456  			return m.Error().MessageID
   457  		case MessageUnboxedState_PLACEHOLDER:
   458  			return m.Placeholder().MessageID
   459  		case MessageUnboxedState_OUTBOX:
   460  			return m.Outbox().Msg.ClientHeader.OutboxInfo.Prev
   461  		case MessageUnboxedState_JOURNEYCARD:
   462  			return m.Journeycard().PrevID
   463  		default:
   464  			return 0
   465  		}
   466  	}
   467  	return 0
   468  }
   469  
   470  func (m MessageUnboxed) IsEphemeral() bool {
   471  	if state, err := m.State(); err == nil {
   472  		switch state {
   473  		case MessageUnboxedState_VALID:
   474  			return m.Valid().IsEphemeral()
   475  		case MessageUnboxedState_ERROR:
   476  			return m.Error().IsEphemeral
   477  		}
   478  	}
   479  	return false
   480  }
   481  
   482  func (m MessageUnboxed) HideExplosion(maxDeletedUpto MessageID, now time.Time) bool {
   483  	if state, err := m.State(); err == nil {
   484  		switch state {
   485  		case MessageUnboxedState_VALID:
   486  			return m.Valid().HideExplosion(maxDeletedUpto, now)
   487  		case MessageUnboxedState_ERROR:
   488  			return m.Error().HideExplosion(maxDeletedUpto, now)
   489  		}
   490  	}
   491  	return false
   492  }
   493  
   494  func (m MessageUnboxed) GetOutboxID() *OutboxID {
   495  	if state, err := m.State(); err == nil {
   496  		switch state {
   497  		case MessageUnboxedState_VALID:
   498  			return m.Valid().ClientHeader.OutboxID
   499  		case MessageUnboxedState_ERROR:
   500  			return nil
   501  		case MessageUnboxedState_PLACEHOLDER:
   502  			return nil
   503  		case MessageUnboxedState_OUTBOX:
   504  			obid := m.Outbox().OutboxID
   505  			return &obid
   506  		case MessageUnboxedState_JOURNEYCARD:
   507  			return nil
   508  		default:
   509  			return nil
   510  		}
   511  	}
   512  	return nil
   513  }
   514  
   515  func (m MessageUnboxed) GetTopicType() TopicType {
   516  	if state, err := m.State(); err == nil {
   517  		switch state {
   518  		case MessageUnboxedState_VALID:
   519  			return m.Valid().ClientHeader.Conv.TopicType
   520  		case MessageUnboxedState_ERROR:
   521  			return TopicType_NONE
   522  		case MessageUnboxedState_OUTBOX:
   523  			return m.Outbox().Msg.ClientHeader.Conv.TopicType
   524  		case MessageUnboxedState_PLACEHOLDER:
   525  			return TopicType_NONE
   526  		case MessageUnboxedState_JOURNEYCARD:
   527  			return TopicType_NONE
   528  		}
   529  	}
   530  	return TopicType_NONE
   531  }
   532  
   533  func (m MessageUnboxed) GetMessageType() MessageType {
   534  	if state, err := m.State(); err == nil {
   535  		switch state {
   536  		case MessageUnboxedState_VALID:
   537  			return m.Valid().ClientHeader.MessageType
   538  		case MessageUnboxedState_ERROR:
   539  			return m.Error().MessageType
   540  		case MessageUnboxedState_OUTBOX:
   541  			return m.Outbox().Msg.ClientHeader.MessageType
   542  		case MessageUnboxedState_PLACEHOLDER:
   543  			// All we know about a place holder is the ID, so just
   544  			// call it type NONE
   545  			return MessageType_NONE
   546  		case MessageUnboxedState_JOURNEYCARD:
   547  			return MessageType_NONE
   548  		}
   549  	}
   550  	return MessageType_NONE
   551  }
   552  
   553  func (m MessageUnboxed) IsValid() bool {
   554  	if state, err := m.State(); err == nil {
   555  		return state == MessageUnboxedState_VALID
   556  	}
   557  	return false
   558  }
   559  
   560  func (m MessageUnboxed) IsError() bool {
   561  	if state, err := m.State(); err == nil {
   562  		return state == MessageUnboxedState_ERROR
   563  	}
   564  	return false
   565  }
   566  
   567  func (m MessageUnboxed) IsOutbox() bool {
   568  	if state, err := m.State(); err == nil {
   569  		return state == MessageUnboxedState_OUTBOX
   570  	}
   571  	return false
   572  }
   573  
   574  func (m MessageUnboxed) IsPlaceholder() bool {
   575  	if state, err := m.State(); err == nil {
   576  		return state == MessageUnboxedState_PLACEHOLDER
   577  	}
   578  	return false
   579  }
   580  
   581  func (m MessageUnboxed) IsJourneycard() bool {
   582  	if state, err := m.State(); err == nil {
   583  		return state == MessageUnboxedState_JOURNEYCARD
   584  	}
   585  	return false
   586  }
   587  
   588  // IsValidFull returns whether the message is both:
   589  //  1. Valid
   590  //  2. Has a non-deleted body with a type matching the header
   591  //     (TLFNAME is an exception as it has no body)
   592  func (m MessageUnboxed) IsValidFull() bool {
   593  	if !m.IsValid() {
   594  		return false
   595  	}
   596  	valid := m.Valid()
   597  	headerType := valid.ClientHeader.MessageType
   598  	switch headerType {
   599  	case MessageType_NONE:
   600  		return false
   601  	case MessageType_TLFNAME:
   602  		// Skip body check
   603  		return true
   604  	}
   605  	bodyType, err := valid.MessageBody.MessageType()
   606  	if err != nil {
   607  		return false
   608  	}
   609  	return bodyType == headerType
   610  }
   611  
   612  // IsValidDeleted returns whether a message is valid and has been deleted.
   613  // This statement does not hold: IsValidFull != IsValidDeleted
   614  func (m MessageUnboxed) IsValidDeleted() bool {
   615  	if !m.IsValid() {
   616  		return false
   617  	}
   618  	valid := m.Valid()
   619  	headerType := valid.ClientHeader.MessageType
   620  	switch headerType {
   621  	case MessageType_NONE:
   622  		return false
   623  	case MessageType_TLFNAME:
   624  		// Undeletable and may have no body
   625  		return false
   626  	}
   627  	bodyType, err := valid.MessageBody.MessageType()
   628  	if err != nil {
   629  		return false
   630  	}
   631  	return bodyType == MessageType_NONE
   632  }
   633  
   634  func (m MessageUnboxed) IsVisible() bool {
   635  	typ := m.GetMessageType()
   636  	for _, visType := range VisibleChatMessageTypes() {
   637  		if typ == visType {
   638  			return true
   639  		}
   640  	}
   641  	return false
   642  }
   643  
   644  func (m MessageUnboxed) HasReactions() bool {
   645  	if !m.IsValid() {
   646  		return false
   647  	}
   648  	return len(m.Valid().Reactions.Reactions) > 0
   649  }
   650  
   651  func (m MessageUnboxed) HasUnfurls() bool {
   652  	if !m.IsValid() {
   653  		return false
   654  	}
   655  	return len(m.Valid().Unfurls) > 0
   656  }
   657  
   658  func (m MessageUnboxed) SearchableText() string {
   659  	if !m.IsValidFull() {
   660  		return ""
   661  	}
   662  	return m.Valid().MessageBody.SearchableText()
   663  }
   664  
   665  func (m MessageUnboxed) SenderUsername() string {
   666  	if !m.IsValid() {
   667  		return ""
   668  	}
   669  	return m.Valid().SenderUsername
   670  }
   671  
   672  func (m MessageUnboxed) Ctime() gregor1.Time {
   673  	if !m.IsValid() {
   674  		return 0
   675  	}
   676  	return m.Valid().ServerHeader.Ctime
   677  }
   678  
   679  func (m MessageUnboxed) AtMentionUsernames() []string {
   680  	if !m.IsValid() {
   681  		return nil
   682  	}
   683  	return m.Valid().AtMentionUsernames
   684  }
   685  
   686  func (m MessageUnboxed) ChannelMention() ChannelMention {
   687  	if !m.IsValid() {
   688  		return ChannelMention_NONE
   689  	}
   690  	return m.Valid().ChannelMention
   691  }
   692  
   693  func (m MessageUnboxed) SenderIsBot() bool {
   694  	if m.IsValid() {
   695  		valid := m.Valid()
   696  		return gregor1.UIDPtrEq(valid.ClientHeader.BotUID, &valid.ClientHeader.Sender)
   697  	}
   698  	return false
   699  }
   700  
   701  func (m *MessageUnboxed) DebugString() string {
   702  	if m == nil {
   703  		return "[nil]"
   704  	}
   705  	state, err := m.State()
   706  	if err != nil {
   707  		return fmt.Sprintf("[INVALID err:%v]", err)
   708  	}
   709  	if state == MessageUnboxedState_ERROR {
   710  		merr := m.Error()
   711  		return fmt.Sprintf("[%v %v mt:%v (%v) (%v)]", state, m.GetMessageID(), merr.ErrType, merr.ErrMsg, merr.InternalErrMsg)
   712  	}
   713  	switch state {
   714  	case MessageUnboxedState_VALID:
   715  		valid := m.Valid()
   716  		headerType := valid.ClientHeader.MessageType
   717  		s := fmt.Sprintf("%v %v", state, valid.ServerHeader.MessageID)
   718  		bodyType, err := valid.MessageBody.MessageType()
   719  		if err != nil {
   720  			return fmt.Sprintf("[INVALID-BODY err:%v]", err)
   721  		}
   722  		if headerType == bodyType {
   723  			s = fmt.Sprintf("%v %v", s, headerType)
   724  		} else {
   725  			if headerType == MessageType_TLFNAME {
   726  				s = fmt.Sprintf("%v h:%v (b:%v)", s, headerType, bodyType)
   727  			} else {
   728  				s = fmt.Sprintf("%v h:%v != b:%v", s, headerType, bodyType)
   729  			}
   730  		}
   731  		if valid.ServerHeader.SupersededBy != 0 {
   732  			s = fmt.Sprintf("%v supBy:%v", s, valid.ServerHeader.SupersededBy)
   733  		}
   734  		return fmt.Sprintf("[%v]", s)
   735  	case MessageUnboxedState_OUTBOX:
   736  		obr := m.Outbox()
   737  		var ostateStr string
   738  		ostate, err := obr.State.State()
   739  		if err != nil {
   740  			ostateStr = "CORRUPT"
   741  		} else {
   742  			ostateStr = fmt.Sprintf("%v", ostate)
   743  		}
   744  		return fmt.Sprintf("[%v obid:%v prev:%v ostate:%v %v]",
   745  			state, obr.OutboxID, obr.Msg.ClientHeader.OutboxInfo.Prev, ostateStr, obr.Msg.ClientHeader.MessageType)
   746  	case MessageUnboxedState_JOURNEYCARD:
   747  		jc := m.Journeycard()
   748  		return fmt.Sprintf("[JOURNEYCARD %v]", jc.CardType)
   749  	default:
   750  		return fmt.Sprintf("[state:%v %v]", state, m.GetMessageID())
   751  	}
   752  }
   753  
   754  func MessageUnboxedDebugStrings(ms []MessageUnboxed) (res []string) {
   755  	for _, m := range ms {
   756  		res = append(res, m.DebugString())
   757  	}
   758  	return res
   759  }
   760  
   761  func MessageUnboxedDebugList(ms []MessageUnboxed) string {
   762  	return fmt.Sprintf("{ %v %v }", len(ms), strings.Join(MessageUnboxedDebugStrings(ms), ","))
   763  }
   764  
   765  func MessageUnboxedDebugLines(ms []MessageUnboxed) string {
   766  	return strings.Join(MessageUnboxedDebugStrings(ms), "\n")
   767  }
   768  
   769  const (
   770  	VersionErrorMessageBoxed VersionKind = "messageboxed"
   771  	VersionErrorHeader       VersionKind = "header"
   772  	VersionErrorBody         VersionKind = "body"
   773  )
   774  
   775  // NOTE: these values correspond to the maximum accepted values in
   776  // chat/boxer.go. If these values are changed, they must also be accepted
   777  // there.
   778  var MaxMessageBoxedVersion MessageBoxedVersion = MessageBoxedVersion_V4
   779  var MaxHeaderVersion HeaderPlaintextVersion = HeaderPlaintextVersion_V1
   780  var MaxBodyVersion BodyPlaintextVersion = BodyPlaintextVersion_V2
   781  
   782  // ParseableVersion checks if this error has a version that is now able to be
   783  // understood by our client.
   784  func (m MessageUnboxedError) ParseableVersion() bool {
   785  	switch m.ErrType {
   786  	case MessageUnboxedErrorType_BADVERSION, MessageUnboxedErrorType_BADVERSION_CRITICAL:
   787  		// only applies to these types
   788  	default:
   789  		return false
   790  	}
   791  
   792  	kind := m.VersionKind
   793  	version := m.VersionNumber
   794  	// This error was stored from an old client, we have parse out the info we
   795  	// need from the error message.
   796  	// TODO remove this check once it has be live for a few cycles.
   797  	if kind == "" && version == 0 {
   798  		re := regexp.MustCompile(`.* Chat version error: \[ unhandled: (\w+) version: (\d+) .*\]`)
   799  		matches := re.FindStringSubmatch(m.ErrMsg)
   800  		if len(matches) != 3 {
   801  			return false
   802  		}
   803  		kind = VersionKind(matches[1])
   804  		var err error
   805  		version, err = strconv.Atoi(matches[2])
   806  		if err != nil {
   807  			return false
   808  		}
   809  	}
   810  
   811  	var maxVersion int
   812  	switch kind {
   813  	case VersionErrorMessageBoxed:
   814  		maxVersion = int(MaxMessageBoxedVersion)
   815  	case VersionErrorHeader:
   816  		maxVersion = int(MaxHeaderVersion)
   817  	case VersionErrorBody:
   818  		maxVersion = int(MaxBodyVersion)
   819  	default:
   820  		return false
   821  	}
   822  	return maxVersion >= version
   823  }
   824  
   825  func (m MessageUnboxedError) IsEphemeralError() bool {
   826  	return m.IsEphemeral && (m.ErrType == MessageUnboxedErrorType_EPHEMERAL || m.ErrType == MessageUnboxedErrorType_PAIRWISE_MISSING)
   827  }
   828  
   829  func (m MessageUnboxedError) IsEphemeralExpired(now time.Time) bool {
   830  	if !m.IsEphemeral {
   831  		return false
   832  	}
   833  	etime := m.Etime.Time()
   834  	// There are a few ways a message could be considered expired
   835  	// 1. We were "exploded now"
   836  	// 2. Our lifetime is up
   837  	return m.ExplodedBy != nil || etime.Before(now) || etime.Equal(now)
   838  }
   839  
   840  func (m MessageUnboxedError) HideExplosion(maxDeletedUpto MessageID, now time.Time) bool {
   841  	if !m.IsEphemeral {
   842  		return false
   843  	}
   844  	etime := m.Etime
   845  	// Don't show ash lines for messages that have been expunged.
   846  	return etime.Time().Add(ShowExplosionLifetime).Before(now) || m.MessageID < maxDeletedUpto
   847  }
   848  
   849  func (m MessageUnboxedError) SenderEq(o MessageUnboxedError) bool {
   850  	return m.SenderUsername == o.SenderUsername
   851  }
   852  
   853  func (m OutboxRecord) SenderEq(o OutboxRecord) bool {
   854  	return m.Msg.ClientHeader.Sender.Eq(o.Msg.ClientHeader.Sender)
   855  }
   856  
   857  func (m MessageUnboxedValid) AsDeleteHistory() (res MessageDeleteHistory, err error) {
   858  	if m.ClientHeader.MessageType != MessageType_DELETEHISTORY {
   859  		return res, fmt.Errorf("message is %v not %v", m.ClientHeader.MessageType, MessageType_DELETEHISTORY)
   860  	}
   861  	if m.MessageBody.IsNil() {
   862  		return res, fmt.Errorf("missing message body")
   863  	}
   864  	btyp, err := m.MessageBody.MessageType()
   865  	if err != nil {
   866  		return res, err
   867  	}
   868  	if btyp != MessageType_DELETEHISTORY {
   869  		return res, fmt.Errorf("message has wrong body type: %v", btyp)
   870  	}
   871  	return m.MessageBody.Deletehistory(), nil
   872  }
   873  
   874  func (m *MsgEphemeralMetadata) String() string {
   875  	if m == nil {
   876  		return "<nil>"
   877  	}
   878  	var explodedBy string
   879  	if m.ExplodedBy == nil {
   880  		explodedBy = "<nil>"
   881  	} else {
   882  		explodedBy = *m.ExplodedBy
   883  	}
   884  	return fmt.Sprintf("{ Lifetime: %v, Generation: %v, ExplodedBy: %v }", m.Lifetime.ToDuration(), m.Generation, explodedBy)
   885  }
   886  
   887  func (m MessagePlaintext) MessageType() MessageType {
   888  	typ, err := m.MessageBody.MessageType()
   889  	if err != nil {
   890  		return MessageType_NONE
   891  	}
   892  	return typ
   893  }
   894  
   895  func (m MessagePlaintext) IsVisible() bool {
   896  	typ := m.MessageType()
   897  	for _, visType := range VisibleChatMessageTypes() {
   898  		if typ == visType {
   899  			return true
   900  		}
   901  	}
   902  	return false
   903  }
   904  
   905  func (m MessagePlaintext) IsBadgableType() bool {
   906  	typ := m.MessageType()
   907  	switch typ {
   908  	case MessageType_TEXT, MessageType_ATTACHMENT:
   909  		return true
   910  	default:
   911  		return false
   912  	}
   913  }
   914  
   915  func (m MessagePlaintext) SearchableText() string {
   916  	return m.MessageBody.SearchableText()
   917  }
   918  
   919  func (m MessagePlaintext) IsEphemeral() bool {
   920  	return m.EphemeralMetadata() != nil
   921  }
   922  
   923  func (m MessagePlaintext) EphemeralMetadata() *MsgEphemeralMetadata {
   924  	return m.ClientHeader.EphemeralMetadata
   925  }
   926  
   927  func (o *MsgEphemeralMetadata) Eq(r *MsgEphemeralMetadata) bool {
   928  	if o != nil && r != nil {
   929  		return *o == *r
   930  	}
   931  	return (o == nil) && (r == nil)
   932  }
   933  
   934  func (m MessageUnboxedValid) SenderEq(o MessageUnboxedValid) bool {
   935  	return m.ClientHeader.Sender.Eq(o.ClientHeader.Sender)
   936  }
   937  
   938  func (m MessageUnboxedValid) HasPairwiseMacs() bool {
   939  	return m.ClientHeader.HasPairwiseMacs
   940  }
   941  
   942  func (m MessageUnboxedValid) IsEphemeral() bool {
   943  	return m.EphemeralMetadata() != nil
   944  }
   945  
   946  func (m MessageUnboxedValid) EphemeralMetadata() *MsgEphemeralMetadata {
   947  	return m.ClientHeader.EphemeralMetadata
   948  }
   949  
   950  func (m MessageUnboxedValid) ExplodedBy() *string {
   951  	if !m.IsEphemeral() {
   952  		return nil
   953  	}
   954  	return m.EphemeralMetadata().ExplodedBy
   955  }
   956  
   957  func Etime(lifetime gregor1.DurationSec, ctime, rtime, now gregor1.Time) gregor1.Time {
   958  	originalLifetime := lifetime.ToDuration()
   959  	elapsedLifetime := now.Time().Sub(ctime.Time())
   960  	remainingLifetime := originalLifetime - elapsedLifetime
   961  	// If the server's view doesn't make sense, just use the signed lifetime
   962  	// from the message.
   963  	if remainingLifetime > originalLifetime {
   964  		remainingLifetime = originalLifetime
   965  	}
   966  	etime := rtime.Time().Add(remainingLifetime)
   967  	return gregor1.ToTime(etime)
   968  }
   969  
   970  func (m MessageUnboxedValid) Etime() gregor1.Time {
   971  	// The server sends us (untrusted) ctime of the message and server's view
   972  	// of now. We use these to calculate the remaining lifetime on an ephemeral
   973  	// message, returning an etime based on our received time.
   974  	metadata := m.EphemeralMetadata()
   975  	if metadata == nil {
   976  		return 0
   977  	}
   978  	header := m.ServerHeader
   979  	return Etime(metadata.Lifetime, header.Ctime, m.ClientHeader.Rtime, header.Now)
   980  }
   981  
   982  func (m MessageUnboxedValid) RemainingEphemeralLifetime(now time.Time) time.Duration {
   983  	remainingLifetime := m.Etime().Time().Sub(now).Round(time.Second)
   984  	return remainingLifetime
   985  }
   986  
   987  func (m MessageUnboxedValid) IsEphemeralExpired(now time.Time) bool {
   988  	if !m.IsEphemeral() {
   989  		return false
   990  	}
   991  	etime := m.Etime().Time()
   992  	// There are a few ways a message could be considered expired
   993  	// 1. Our body is already nil (deleted from DELETEHISTORY or a server purge)
   994  	// 2. We were "exploded now"
   995  	// 3. Our lifetime is up
   996  	return m.MessageBody.IsNil() || m.EphemeralMetadata().ExplodedBy != nil || etime.Before(now) || etime.Equal(now)
   997  }
   998  
   999  func (m MessageUnboxedValid) HideExplosion(maxDeletedUpto MessageID, now time.Time) bool {
  1000  	if !m.IsEphemeral() {
  1001  		return false
  1002  	}
  1003  	etime := m.Etime()
  1004  	// Don't show ash lines for messages that have been expunged.
  1005  	return etime.Time().Add(ShowExplosionLifetime).Before(now) || m.ServerHeader.MessageID < maxDeletedUpto
  1006  }
  1007  
  1008  func (b MessageBody) IsNil() bool {
  1009  	return b == MessageBody{}
  1010  }
  1011  
  1012  func (b MessageBody) IsType(typ MessageType) bool {
  1013  	btyp, err := b.MessageType()
  1014  	if err != nil {
  1015  		return false
  1016  	}
  1017  	return btyp == typ
  1018  }
  1019  
  1020  func (b MessageBody) TextForDecoration() string {
  1021  	typ, err := b.MessageType()
  1022  	if err != nil {
  1023  		return ""
  1024  	}
  1025  	switch typ {
  1026  	case MessageType_REACTION:
  1027  		return b.Reaction().Body
  1028  	case MessageType_HEADLINE:
  1029  		return b.Headline().Headline
  1030  	case MessageType_ATTACHMENT:
  1031  		// Exclude the filename for text decoration.
  1032  		return b.Attachment().Object.Title
  1033  	default:
  1034  		return b.SearchableText()
  1035  	}
  1036  }
  1037  
  1038  func (b MessageBody) SearchableText() string {
  1039  	typ, err := b.MessageType()
  1040  	if err != nil {
  1041  		return ""
  1042  	}
  1043  	switch typ {
  1044  	case MessageType_TEXT:
  1045  		return b.Text().Body
  1046  	case MessageType_EDIT:
  1047  		return b.Edit().Body
  1048  	case MessageType_REQUESTPAYMENT:
  1049  		return b.Requestpayment().Note
  1050  	case MessageType_ATTACHMENT:
  1051  		return b.Attachment().GetTitle()
  1052  	case MessageType_FLIP:
  1053  		return b.Flip().Text
  1054  	case MessageType_UNFURL:
  1055  		return b.Unfurl().SearchableText()
  1056  	case MessageType_SYSTEM:
  1057  		return b.System().String()
  1058  	default:
  1059  		return ""
  1060  	}
  1061  }
  1062  
  1063  func (b MessageBody) GetEmojis() map[string]HarvestedEmoji {
  1064  	typ, err := b.MessageType()
  1065  	if err != nil {
  1066  		return nil
  1067  	}
  1068  	switch typ {
  1069  	case MessageType_TEXT:
  1070  		return b.Text().Emojis
  1071  	case MessageType_REACTION:
  1072  		return b.Reaction().Emojis
  1073  	case MessageType_EDIT:
  1074  		return b.Edit().Emojis
  1075  	case MessageType_ATTACHMENT:
  1076  		return b.Attachment().Emojis
  1077  	case MessageType_HEADLINE:
  1078  		return b.Headline().Emojis
  1079  	default:
  1080  		return nil
  1081  	}
  1082  }
  1083  
  1084  func (m UIMessage) IsValid() bool {
  1085  	if state, err := m.State(); err == nil {
  1086  		return state == MessageUnboxedState_VALID
  1087  	}
  1088  	return false
  1089  }
  1090  
  1091  func (m UIMessage) IsError() bool {
  1092  	if state, err := m.State(); err == nil {
  1093  		return state == MessageUnboxedState_ERROR
  1094  	}
  1095  	return false
  1096  }
  1097  
  1098  func (m UIMessage) IsOutbox() bool {
  1099  	if state, err := m.State(); err == nil {
  1100  		return state == MessageUnboxedState_OUTBOX
  1101  	}
  1102  	return false
  1103  }
  1104  
  1105  func (m UIMessage) IsPlaceholder() bool {
  1106  	if state, err := m.State(); err == nil {
  1107  		return state == MessageUnboxedState_PLACEHOLDER
  1108  	}
  1109  	return false
  1110  }
  1111  
  1112  func (m UIMessage) GetMessageID() MessageID {
  1113  	if state, err := m.State(); err == nil {
  1114  		if state == MessageUnboxedState_VALID {
  1115  			return m.Valid().MessageID
  1116  		}
  1117  		if state == MessageUnboxedState_ERROR {
  1118  			return m.Error().MessageID
  1119  		}
  1120  		if state == MessageUnboxedState_PLACEHOLDER {
  1121  			return m.Placeholder().MessageID
  1122  		}
  1123  	}
  1124  	return 0
  1125  }
  1126  
  1127  func (m UIMessage) GetOutboxID() *OutboxID {
  1128  	if state, err := m.State(); err == nil {
  1129  		if state == MessageUnboxedState_VALID {
  1130  			strOutboxID := m.Valid().OutboxID
  1131  			if strOutboxID != nil {
  1132  				outboxID, err := MakeOutboxID(*strOutboxID)
  1133  				if err != nil {
  1134  					return nil
  1135  				}
  1136  				return &outboxID
  1137  			}
  1138  			return nil
  1139  		}
  1140  		if state == MessageUnboxedState_ERROR {
  1141  			return nil
  1142  		}
  1143  		if state == MessageUnboxedState_PLACEHOLDER {
  1144  			return nil
  1145  		}
  1146  	}
  1147  	return nil
  1148  }
  1149  
  1150  func (m UIMessage) GetMessageType() MessageType {
  1151  	state, err := m.State()
  1152  	if err != nil {
  1153  		return MessageType_NONE
  1154  	}
  1155  	switch state {
  1156  	case MessageUnboxedState_VALID:
  1157  		body := m.Valid().MessageBody
  1158  		typ, err := body.MessageType()
  1159  		if err != nil {
  1160  			return MessageType_NONE
  1161  		}
  1162  		return typ
  1163  	case MessageUnboxedState_ERROR:
  1164  		return m.Error().MessageType
  1165  	case MessageUnboxedState_OUTBOX:
  1166  		return m.Outbox().MessageType
  1167  	default:
  1168  		return MessageType_NONE
  1169  	}
  1170  }
  1171  
  1172  func (m UIMessage) SearchableText() string {
  1173  	if !m.IsValid() {
  1174  		return ""
  1175  	}
  1176  	return m.Valid().MessageBody.SearchableText()
  1177  }
  1178  
  1179  func (m UIMessage) IsEphemeral() bool {
  1180  	state, err := m.State()
  1181  	if err != nil {
  1182  		return false
  1183  	}
  1184  	switch state {
  1185  	case MessageUnboxedState_VALID:
  1186  		return m.Valid().IsEphemeral
  1187  	case MessageUnboxedState_ERROR:
  1188  		return m.Error().IsEphemeral
  1189  	default:
  1190  		return false
  1191  	}
  1192  }
  1193  
  1194  func (m MessageBoxed) GetMessageID() MessageID {
  1195  	return m.ServerHeader.MessageID
  1196  }
  1197  
  1198  func (m MessageBoxed) Ctime() gregor1.Time {
  1199  	return m.ServerHeader.Ctime
  1200  }
  1201  
  1202  func (m MessageBoxed) GetMessageType() MessageType {
  1203  	return m.ClientHeader.MessageType
  1204  }
  1205  
  1206  func (m MessageBoxed) Summary() MessageSummary {
  1207  	s := MessageSummary{
  1208  		MsgID:       m.GetMessageID(),
  1209  		MessageType: m.GetMessageType(),
  1210  		TlfName:     m.ClientHeader.TlfName,
  1211  		TlfPublic:   m.ClientHeader.TlfPublic,
  1212  	}
  1213  	if m.ServerHeader != nil {
  1214  		s.Ctime = m.ServerHeader.Ctime
  1215  	}
  1216  	return s
  1217  }
  1218  
  1219  func (m MessageBoxed) OutboxInfo() *OutboxInfo {
  1220  	return m.ClientHeader.OutboxInfo
  1221  }
  1222  
  1223  func (m MessageBoxed) KBFSEncrypted() bool {
  1224  	return m.ClientHeader.KbfsCryptKeysUsed == nil || *m.ClientHeader.KbfsCryptKeysUsed
  1225  }
  1226  
  1227  func (m MessageBoxed) EphemeralMetadata() *MsgEphemeralMetadata {
  1228  	return m.ClientHeader.EphemeralMetadata
  1229  }
  1230  
  1231  func (m MessageBoxed) IsEphemeral() bool {
  1232  	return m.EphemeralMetadata() != nil
  1233  }
  1234  
  1235  func (m MessageBoxed) Etime() gregor1.Time {
  1236  	// The server sends us (untrusted) ctime of the message and server's view
  1237  	// of now. We use these to calculate the remaining lifetime on an ephemeral
  1238  	// message, returning an etime based on the current time.
  1239  	metadata := m.EphemeralMetadata()
  1240  	if metadata == nil {
  1241  		return 0
  1242  	}
  1243  	rtime := gregor1.ToTime(time.Now())
  1244  	if m.ServerHeader.Rtime != nil {
  1245  		rtime = *m.ServerHeader.Rtime
  1246  	}
  1247  	return Etime(metadata.Lifetime, m.ServerHeader.Ctime, rtime, m.ServerHeader.Now)
  1248  }
  1249  
  1250  func (m MessageBoxed) IsEphemeralExpired(now time.Time) bool {
  1251  	if !m.IsEphemeral() {
  1252  		return false
  1253  	}
  1254  	etime := m.Etime().Time()
  1255  	return m.EphemeralMetadata().ExplodedBy != nil || etime.Before(now) || etime.Equal(now)
  1256  }
  1257  
  1258  func (m MessageBoxed) ExplodedBy() *string {
  1259  	if !m.IsEphemeral() {
  1260  		return nil
  1261  	}
  1262  	return m.EphemeralMetadata().ExplodedBy
  1263  }
  1264  
  1265  var ConversationStatusGregorMap = map[ConversationStatus]string{
  1266  	ConversationStatus_UNFILED:  "unfiled",
  1267  	ConversationStatus_FAVORITE: "favorite",
  1268  	ConversationStatus_IGNORED:  "ignored",
  1269  	ConversationStatus_BLOCKED:  "blocked",
  1270  	ConversationStatus_MUTED:    "muted",
  1271  	ConversationStatus_REPORTED: "reported",
  1272  }
  1273  
  1274  var ConversationStatusGregorRevMap = map[string]ConversationStatus{
  1275  	"unfiled":  ConversationStatus_UNFILED,
  1276  	"favorite": ConversationStatus_FAVORITE,
  1277  	"ignored":  ConversationStatus_IGNORED,
  1278  	"blocked":  ConversationStatus_BLOCKED,
  1279  	"muted":    ConversationStatus_MUTED,
  1280  	"reported": ConversationStatus_REPORTED,
  1281  }
  1282  
  1283  var sha256Pool = sync.Pool{
  1284  	New: func() interface{} {
  1285  		return sha256.New()
  1286  	},
  1287  }
  1288  
  1289  func (t ConversationIDTriple) Hash() []byte {
  1290  	h := sha256Pool.Get().(hash.Hash)
  1291  	defer sha256Pool.Put(h)
  1292  	h.Reset()
  1293  	h.Write(t.Tlfid)
  1294  	h.Write(t.TopicID)
  1295  	h.Write([]byte(strconv.Itoa(int(t.TopicType))))
  1296  	hash := h.Sum(nil)
  1297  
  1298  	return hash
  1299  }
  1300  
  1301  func (t ConversationIDTriple) ToConversationID(shardID [2]byte) ConversationID {
  1302  	h := t.Hash()
  1303  	h[0], h[1] = shardID[0], shardID[1]
  1304  	return ConversationID(h)
  1305  }
  1306  
  1307  func (t ConversationIDTriple) Derivable(cid ConversationID) bool {
  1308  	h := t.Hash()
  1309  	if len(h) <= 2 || len(cid) <= 2 {
  1310  		return false
  1311  	}
  1312  	return bytes.Equal(h[2:], []byte(cid[2:]))
  1313  }
  1314  
  1315  func MakeOutboxID(s string) (OutboxID, error) {
  1316  	b, err := hex.DecodeString(s)
  1317  	return OutboxID(b), err
  1318  }
  1319  
  1320  func (o *OutboxID) Eq(r *OutboxID) bool {
  1321  	if o != nil && r != nil {
  1322  		return bytes.Equal(*o, *r)
  1323  	}
  1324  	return (o == nil) && (r == nil)
  1325  }
  1326  
  1327  func (o OutboxID) String() string {
  1328  	return hex.EncodeToString(o)
  1329  }
  1330  
  1331  func (o OutboxID) Bytes() []byte {
  1332  	return []byte(o)
  1333  }
  1334  
  1335  func (o *OutboxInfo) Eq(r *OutboxInfo) bool {
  1336  	if o != nil && r != nil {
  1337  		return *o == *r
  1338  	}
  1339  	return (o == nil) && (r == nil)
  1340  }
  1341  
  1342  func (o OutboxRecord) IsError() bool {
  1343  	state, err := o.State.State()
  1344  	if err != nil {
  1345  		return false
  1346  	}
  1347  	return state == OutboxStateType_ERROR
  1348  }
  1349  
  1350  func (o OutboxRecord) IsSending() bool {
  1351  	state, err := o.State.State()
  1352  	if err != nil {
  1353  		return false
  1354  	}
  1355  	return state == OutboxStateType_SENDING
  1356  }
  1357  
  1358  func (o OutboxRecord) IsAttachment() bool {
  1359  	return o.Msg.ClientHeader.MessageType == MessageType_ATTACHMENT
  1360  }
  1361  
  1362  func (o OutboxRecord) IsUnfurl() bool {
  1363  	return o.Msg.ClientHeader.MessageType == MessageType_UNFURL
  1364  }
  1365  
  1366  func (o OutboxRecord) IsChatFlip() bool {
  1367  	return o.Msg.ClientHeader.MessageType == MessageType_FLIP &&
  1368  		o.Msg.ClientHeader.Conv.TopicType == TopicType_CHAT
  1369  }
  1370  
  1371  func (o OutboxRecord) MessageType() MessageType {
  1372  	return o.Msg.ClientHeader.MessageType
  1373  }
  1374  
  1375  func (o OutboxRecord) IsBadgable() bool {
  1376  	return o.Msg.ClientHeader.Conv.TopicType == TopicType_CHAT &&
  1377  		o.Msg.IsBadgableType()
  1378  }
  1379  
  1380  func (p MessagePreviousPointer) Eq(other MessagePreviousPointer) bool {
  1381  	return (p.Id == other.Id) && (p.Hash.Eq(other.Hash))
  1382  }
  1383  
  1384  // Visibility is a helper to get around a nil pointer for visibility,
  1385  // and to get around TLFVisibility_ANY.  The default is PRIVATE.
  1386  // Note:  not sure why visibility is a pointer, or what TLFVisibility_ANY
  1387  // is for, but don't want to change the API.
  1388  func (q *GetInboxLocalQuery) Visibility() keybase1.TLFVisibility {
  1389  	visibility := keybase1.TLFVisibility_PRIVATE
  1390  	if q.TlfVisibility != nil && *q.TlfVisibility == keybase1.TLFVisibility_PUBLIC {
  1391  		visibility = keybase1.TLFVisibility_PUBLIC
  1392  	}
  1393  	return visibility
  1394  }
  1395  
  1396  // Visibility is a helper to get around a nil pointer for visibility,
  1397  // and to get around TLFVisibility_ANY.  The default is PRIVATE.
  1398  // Note:  not sure why visibility is a pointer, or what TLFVisibility_ANY
  1399  // is for, but don't want to change the API.
  1400  func (q *GetInboxQuery) Visibility() keybase1.TLFVisibility {
  1401  	visibility := keybase1.TLFVisibility_PRIVATE
  1402  	if q.TlfVisibility != nil && *q.TlfVisibility == keybase1.TLFVisibility_PUBLIC {
  1403  		visibility = keybase1.TLFVisibility_PUBLIC
  1404  	}
  1405  	return visibility
  1406  }
  1407  
  1408  // TLFNameExpanded returns a TLF name with a reset suffix if it exists.
  1409  // This version can be used in requests to lookup the TLF.
  1410  func (c ConversationInfoLocal) TLFNameExpanded() string {
  1411  	return ExpandTLFName(c.TlfName, c.FinalizeInfo)
  1412  }
  1413  
  1414  // TLFNameExpandedSummary returns a TLF name with a summary of the
  1415  // account reset if there was one.
  1416  // This version is for display purposes only and cannot be used to lookup the TLF.
  1417  func (c ConversationInfoLocal) TLFNameExpandedSummary() string {
  1418  	if c.FinalizeInfo == nil {
  1419  		return c.TlfName
  1420  	}
  1421  	return c.TlfName + " " + c.FinalizeInfo.BeforeSummary()
  1422  }
  1423  
  1424  // TLFNameExpanded returns a TLF name with a reset suffix if it exists.
  1425  // This version can be used in requests to lookup the TLF.
  1426  func (h MessageClientHeader) TLFNameExpanded(finalizeInfo *ConversationFinalizeInfo) string {
  1427  	return ExpandTLFName(h.TlfName, finalizeInfo)
  1428  }
  1429  
  1430  // TLFNameExpanded returns a TLF name with a reset suffix if it exists.
  1431  // This version can be used in requests to lookup the TLF.
  1432  func (m MessageSummary) TLFNameExpanded(finalizeInfo *ConversationFinalizeInfo) string {
  1433  	return ExpandTLFName(m.TlfName, finalizeInfo)
  1434  }
  1435  
  1436  func (h MessageClientHeaderVerified) TLFNameExpanded(finalizeInfo *ConversationFinalizeInfo) string {
  1437  	return ExpandTLFName(h.TlfName, finalizeInfo)
  1438  }
  1439  
  1440  func (h MessageClientHeader) ToVerifiedForTesting() MessageClientHeaderVerified {
  1441  	if flag.Lookup("test.v") == nil {
  1442  		panic("MessageClientHeader.ToVerifiedForTesting used outside of test")
  1443  	}
  1444  	return MessageClientHeaderVerified{
  1445  		Conv:         h.Conv,
  1446  		TlfName:      h.TlfName,
  1447  		TlfPublic:    h.TlfPublic,
  1448  		MessageType:  h.MessageType,
  1449  		Prev:         h.Prev,
  1450  		Sender:       h.Sender,
  1451  		SenderDevice: h.SenderDevice,
  1452  		OutboxID:     h.OutboxID,
  1453  		OutboxInfo:   h.OutboxInfo,
  1454  	}
  1455  }
  1456  
  1457  // ExpandTLFName returns a TLF name with a reset suffix if it exists.
  1458  // This version can be used in requests to lookup the TLF.
  1459  func ExpandTLFName(name string, finalizeInfo *ConversationFinalizeInfo) string {
  1460  	if finalizeInfo == nil {
  1461  		return name
  1462  	}
  1463  	if len(finalizeInfo.ResetFull) == 0 {
  1464  		return name
  1465  	}
  1466  	if strings.Contains(name, " account reset ") {
  1467  		return name
  1468  	}
  1469  	return name + " " + finalizeInfo.ResetFull
  1470  }
  1471  
  1472  // BeforeSummary returns a summary of the finalize without "files" in it.
  1473  // The canonical name for a TLF after reset has a "(files before ... account reset...)" suffix
  1474  // which doesn't make much sense in other uses (like chat).
  1475  func (f *ConversationFinalizeInfo) BeforeSummary() string {
  1476  	return fmt.Sprintf("(before %s account reset %s)", f.ResetUser, f.ResetDate)
  1477  }
  1478  
  1479  func (f *ConversationFinalizeInfo) IsResetForUser(username string) bool {
  1480  	// If reset user is the given user, or is blank (only way such a thing
  1481  	// could be in our inbox is if the current user is the one that reset)
  1482  	return f != nil && (f.ResetUser == username || f.ResetUser == "")
  1483  }
  1484  
  1485  func (p *Pagination) Eq(other *Pagination) bool {
  1486  	if p == nil && other == nil {
  1487  		return true
  1488  	}
  1489  	if p != nil && other != nil {
  1490  		return p.Last == other.Last && bytes.Equal(p.Next, other.Next) &&
  1491  			bytes.Equal(p.Previous, other.Previous) && p.Num == other.Num
  1492  	}
  1493  	return false
  1494  }
  1495  
  1496  func (p *Pagination) String() string {
  1497  	if p == nil {
  1498  		return "<nil>"
  1499  	}
  1500  	return fmt.Sprintf("[Num: %d n: %s p: %s last: %v]", p.Num, hex.EncodeToString(p.Next),
  1501  		hex.EncodeToString(p.Previous), p.Last)
  1502  }
  1503  
  1504  // FirstPage returns true if the pagination object is not pointing in any direction
  1505  func (p *Pagination) FirstPage() bool {
  1506  	return p == nil || p.ForceFirstPage || (len(p.Next) == 0 && len(p.Previous) == 0)
  1507  }
  1508  
  1509  func (c ConversationLocal) GetMtime() gregor1.Time {
  1510  	return c.ReaderInfo.Mtime
  1511  }
  1512  
  1513  func (c ConversationLocal) GetConvID() ConversationID {
  1514  	return c.Info.Id
  1515  }
  1516  
  1517  func (c ConversationLocal) GetTopicType() TopicType {
  1518  	return c.Info.Triple.TopicType
  1519  }
  1520  
  1521  func (c ConversationLocal) GetMembersType() ConversationMembersType {
  1522  	return c.Info.MembersType
  1523  }
  1524  
  1525  func (c ConversationLocal) GetTeamType() TeamType {
  1526  	return c.Info.TeamType
  1527  }
  1528  
  1529  func (c ConversationLocal) GetFinalizeInfo() *ConversationFinalizeInfo {
  1530  	return c.Info.FinalizeInfo
  1531  }
  1532  
  1533  func (c ConversationLocal) GetTopicName() string {
  1534  	return c.Info.TopicName
  1535  }
  1536  
  1537  func (c ConversationLocal) GetExpunge() *Expunge {
  1538  	return &c.Expunge
  1539  }
  1540  
  1541  func (c ConversationLocal) IsPublic() bool {
  1542  	return c.Info.Visibility == keybase1.TLFVisibility_PUBLIC
  1543  }
  1544  
  1545  func (c ConversationLocal) GetMaxMessage(typ MessageType) (MessageSummary, error) {
  1546  	for _, msg := range c.MaxMessages {
  1547  		if msg.GetMessageType() == typ {
  1548  			return msg, nil
  1549  		}
  1550  	}
  1551  	return MessageSummary{}, fmt.Errorf("max message not found: %v", typ)
  1552  }
  1553  
  1554  func (c ConversationLocal) GetMaxDeletedUpTo() MessageID {
  1555  	var maxExpungeID, maxDelHID MessageID
  1556  	if expunge := c.GetExpunge(); expunge != nil {
  1557  		maxExpungeID = expunge.Upto
  1558  	}
  1559  	if maxDelH, err := c.GetMaxMessage(MessageType_DELETEHISTORY); err != nil {
  1560  		maxDelHID = maxDelH.GetMessageID()
  1561  	}
  1562  	if maxExpungeID > maxDelHID {
  1563  		return maxExpungeID
  1564  	}
  1565  	return maxDelHID
  1566  }
  1567  
  1568  func maxVisibleMsgIDFromSummaries(maxMessages []MessageSummary) MessageID {
  1569  	visibleTyps := VisibleChatMessageTypes()
  1570  	visibleTypsMap := map[MessageType]bool{}
  1571  	for _, typ := range visibleTyps {
  1572  		visibleTypsMap[typ] = true
  1573  	}
  1574  	maxMsgID := MessageID(0)
  1575  	for _, msg := range maxMessages {
  1576  		if _, ok := visibleTypsMap[msg.GetMessageType()]; ok && msg.GetMessageID() > maxMsgID {
  1577  			maxMsgID = msg.GetMessageID()
  1578  		}
  1579  	}
  1580  	return maxMsgID
  1581  }
  1582  
  1583  func (c ConversationLocal) MaxVisibleMsgID() MessageID {
  1584  	return maxVisibleMsgIDFromSummaries(c.MaxMessages)
  1585  }
  1586  
  1587  func (c ConversationLocal) ConvNameNames() (res []string) {
  1588  	for _, p := range c.Info.Participants {
  1589  		if p.InConvName {
  1590  			res = append(res, p.Username)
  1591  		}
  1592  	}
  1593  	return res
  1594  }
  1595  
  1596  func (c ConversationLocal) AllNames() (res []string) {
  1597  	for _, p := range c.Info.Participants {
  1598  		res = append(res, p.Username)
  1599  	}
  1600  	return res
  1601  }
  1602  
  1603  func (c ConversationLocal) FullNamesForSearch() (res []*string) {
  1604  	for _, p := range c.Info.Participants {
  1605  		res = append(res, p.Fullname)
  1606  	}
  1607  	return res
  1608  }
  1609  
  1610  func (c ConversationLocal) CannotWrite() bool {
  1611  	if c.ConvSettings == nil || c.ConvSettings.MinWriterRoleInfo == nil {
  1612  		return false
  1613  	}
  1614  
  1615  	return c.ConvSettings.MinWriterRoleInfo.CannotWrite
  1616  }
  1617  
  1618  func (c Conversation) CannotWrite() bool {
  1619  	if c.ConvSettings == nil || c.ConvSettings.MinWriterRoleInfo == nil || c.ReaderInfo == nil {
  1620  		return false
  1621  	}
  1622  
  1623  	return !c.ReaderInfo.UntrustedTeamRole.IsOrAbove(c.ConvSettings.MinWriterRoleInfo.Role)
  1624  }
  1625  
  1626  func (c Conversation) GetMtime() gregor1.Time {
  1627  	return c.ReaderInfo.Mtime
  1628  }
  1629  
  1630  func (c Conversation) GetConvID() ConversationID {
  1631  	return c.Metadata.ConversationID
  1632  }
  1633  
  1634  func (c Conversation) GetTopicType() TopicType {
  1635  	return c.Metadata.IdTriple.TopicType
  1636  }
  1637  
  1638  func (c Conversation) GetMembersType() ConversationMembersType {
  1639  	return c.Metadata.MembersType
  1640  }
  1641  
  1642  func (c Conversation) GetTeamType() TeamType {
  1643  	return c.Metadata.TeamType
  1644  }
  1645  
  1646  func (c Conversation) GetFinalizeInfo() *ConversationFinalizeInfo {
  1647  	return c.Metadata.FinalizeInfo
  1648  }
  1649  
  1650  func (c Conversation) GetExpunge() *Expunge {
  1651  	return &c.Expunge
  1652  }
  1653  
  1654  func (c Conversation) IsPublic() bool {
  1655  	return c.Metadata.Visibility == keybase1.TLFVisibility_PUBLIC
  1656  }
  1657  
  1658  var errMaxMessageNotFound = errors.New("max message not found")
  1659  
  1660  func (c Conversation) GetMaxMessage(typ MessageType) (MessageSummary, error) {
  1661  	for _, msg := range c.MaxMsgSummaries {
  1662  		if msg.GetMessageType() == typ {
  1663  			return msg, nil
  1664  		}
  1665  	}
  1666  	return MessageSummary{}, errMaxMessageNotFound
  1667  }
  1668  
  1669  func (c Conversation) Includes(uid gregor1.UID) bool {
  1670  	for _, auid := range c.Metadata.ActiveList {
  1671  		if uid.Eq(auid) {
  1672  			return true
  1673  		}
  1674  	}
  1675  	return false
  1676  }
  1677  
  1678  func (c Conversation) GetMaxDeletedUpTo() MessageID {
  1679  	var maxExpungeID, maxDelHID MessageID
  1680  	if expunge := c.GetExpunge(); expunge != nil {
  1681  		maxExpungeID = expunge.Upto
  1682  	}
  1683  	if maxDelH, err := c.GetMaxMessage(MessageType_DELETEHISTORY); err != nil {
  1684  		maxDelHID = maxDelH.GetMessageID()
  1685  	}
  1686  	if maxExpungeID > maxDelHID {
  1687  		return maxExpungeID
  1688  	}
  1689  	return maxDelHID
  1690  }
  1691  
  1692  func (c Conversation) GetMaxMessageID() MessageID {
  1693  	maxMsgID := MessageID(0)
  1694  	for _, msg := range c.MaxMsgSummaries {
  1695  		if msg.GetMessageID() > maxMsgID {
  1696  			maxMsgID = msg.GetMessageID()
  1697  		}
  1698  	}
  1699  	return maxMsgID
  1700  }
  1701  
  1702  func (c Conversation) IsSelfFinalized(username string) bool {
  1703  	return c.GetMembersType() == ConversationMembersType_KBFS && c.GetFinalizeInfo().IsResetForUser(username)
  1704  }
  1705  
  1706  func (c Conversation) MaxVisibleMsgID() MessageID {
  1707  	return maxVisibleMsgIDFromSummaries(c.MaxMsgSummaries)
  1708  }
  1709  
  1710  func (c Conversation) IsUnread() bool {
  1711  	return c.IsUnreadFromMsgID(c.ReaderInfo.ReadMsgid)
  1712  }
  1713  
  1714  func (c Conversation) IsUnreadFromMsgID(readMsgID MessageID) bool {
  1715  	maxMsgID := c.MaxVisibleMsgID()
  1716  	return maxMsgID > 0 && maxMsgID > readMsgID
  1717  }
  1718  
  1719  func (c Conversation) HasMemberStatus(status ConversationMemberStatus) bool {
  1720  	if c.ReaderInfo != nil {
  1721  		return c.ReaderInfo.Status == status
  1722  	}
  1723  	return false
  1724  }
  1725  
  1726  func (m MessageSummary) GetMessageID() MessageID {
  1727  	return m.MsgID
  1728  }
  1729  
  1730  func (m MessageSummary) GetMessageType() MessageType {
  1731  	return m.MessageType
  1732  }
  1733  
  1734  /*
  1735  func ConvertMessageBodyV1ToV2(v1 MessageBodyV1) (MessageBody, error) {
  1736  	t, err := v1.MessageType()
  1737  	if err != nil {
  1738  		return MessageBody{}, err
  1739  	}
  1740  	switch t {
  1741  	case MessageType_TEXT:
  1742  		return NewMessageBodyWithText(v1.Text()), nil
  1743  	case MessageType_ATTACHMENT:
  1744  		previous := v1.Attachment()
  1745  		upgraded := MessageAttachment{
  1746  			Object:   previous.Object,
  1747  			Metadata: previous.Metadata,
  1748  			Uploaded: true,
  1749  		}
  1750  		if previous.Preview != nil {
  1751  			upgraded.Previews = []Asset{*previous.Preview}
  1752  		}
  1753  		return NewMessageBodyWithAttachment(upgraded), nil
  1754  	case MessageType_EDIT:
  1755  		return NewMessageBodyWithEdit(v1.Edit()), nil
  1756  	case MessageType_DELETE:
  1757  		return NewMessageBodyWithDelete(v1.Delete()), nil
  1758  	case MessageType_METADATA:
  1759  		return NewMessageBodyWithMetadata(v1.Metadata()), nil
  1760  	case MessageType_HEADLINE:
  1761  		return NewMessageBodyWithHeadline(v1.Headline()), nil
  1762  	case MessageType_NONE:
  1763  		return MessageBody{MessageType__: MessageType_NONE}, nil
  1764  	}
  1765  
  1766  	return MessageBody{}, fmt.Errorf("ConvertMessageBodyV1ToV2: unhandled message type %v", t)
  1767  }
  1768  */
  1769  
  1770  func (a *MerkleRoot) Eq(b *MerkleRoot) bool {
  1771  	if a != nil && b != nil {
  1772  		return (a.Seqno == b.Seqno) && bytes.Equal(a.Hash, b.Hash)
  1773  	}
  1774  	return (a == nil) && (b == nil)
  1775  }
  1776  
  1777  func (d *SealedData) AsEncrypted() EncryptedData {
  1778  	return EncryptedData{
  1779  		V: d.V,
  1780  		E: d.E,
  1781  		N: d.N,
  1782  	}
  1783  }
  1784  
  1785  func (d *SealedData) AsSignEncrypted() SignEncryptedData {
  1786  	return SignEncryptedData{
  1787  		V: d.V,
  1788  		E: d.E,
  1789  		N: d.N,
  1790  	}
  1791  }
  1792  
  1793  func (d *EncryptedData) AsSealed() SealedData {
  1794  	return SealedData{
  1795  		V: d.V,
  1796  		E: d.E,
  1797  		N: d.N,
  1798  	}
  1799  }
  1800  
  1801  func (d *SignEncryptedData) AsSealed() SealedData {
  1802  	return SealedData{
  1803  		V: d.V,
  1804  		E: d.E,
  1805  		N: d.N,
  1806  	}
  1807  }
  1808  
  1809  func NewConversationErrorLocal(
  1810  	message string,
  1811  	remoteConv Conversation,
  1812  	unverifiedTLFName string,
  1813  	typ ConversationErrorType,
  1814  	rekeyInfo *ConversationErrorRekey,
  1815  ) *ConversationErrorLocal {
  1816  	return &ConversationErrorLocal{
  1817  		Typ:               typ,
  1818  		Message:           message,
  1819  		RemoteConv:        remoteConv,
  1820  		UnverifiedTLFName: unverifiedTLFName,
  1821  		RekeyInfo:         rekeyInfo,
  1822  	}
  1823  }
  1824  
  1825  type OfflinableResult interface {
  1826  	SetOffline()
  1827  }
  1828  
  1829  func (r *NonblockFetchRes) SetOffline() {
  1830  	r.Offline = true
  1831  }
  1832  
  1833  func (r *UnreadlineRes) SetOffline() {
  1834  	r.Offline = true
  1835  }
  1836  
  1837  func (r *MarkAsReadLocalRes) SetOffline() {
  1838  	r.Offline = true
  1839  }
  1840  
  1841  func (r *MarkTLFAsReadLocalRes) SetOffline() {
  1842  	r.Offline = true
  1843  }
  1844  
  1845  func (r *GetInboxAndUnboxLocalRes) SetOffline() {
  1846  	r.Offline = true
  1847  }
  1848  
  1849  func (r *GetInboxAndUnboxUILocalRes) SetOffline() {
  1850  	r.Offline = true
  1851  }
  1852  
  1853  func (r *GetThreadLocalRes) SetOffline() {
  1854  	r.Offline = true
  1855  }
  1856  
  1857  func (r *GetInboxSummaryForCLILocalRes) SetOffline() {
  1858  	r.Offline = true
  1859  }
  1860  
  1861  func (r *GetConversationForCLILocalRes) SetOffline() {
  1862  	r.Offline = true
  1863  }
  1864  
  1865  func (r *GetMessagesLocalRes) SetOffline() {
  1866  	r.Offline = true
  1867  }
  1868  
  1869  func (r *GetNextAttachmentMessageLocalRes) SetOffline() {
  1870  	r.Offline = true
  1871  }
  1872  
  1873  func (r *FindConversationsLocalRes) SetOffline() {
  1874  	r.Offline = true
  1875  }
  1876  
  1877  func (r *JoinLeaveConversationLocalRes) SetOffline() {
  1878  	r.Offline = true
  1879  }
  1880  
  1881  func (r *PreviewConversationLocalRes) SetOffline() {
  1882  	r.Offline = true
  1883  }
  1884  
  1885  func (r *GetTLFConversationsLocalRes) SetOffline() {
  1886  	r.Offline = true
  1887  }
  1888  
  1889  func (r *GetChannelMembershipsLocalRes) SetOffline() {
  1890  	r.Offline = true
  1891  }
  1892  
  1893  func (r *GetMutualTeamsLocalRes) SetOffline() {
  1894  	r.Offline = true
  1895  }
  1896  
  1897  func (r *SetAppNotificationSettingsLocalRes) SetOffline() {
  1898  	r.Offline = true
  1899  }
  1900  
  1901  func (r *DeleteConversationLocalRes) SetOffline() {
  1902  	r.Offline = true
  1903  }
  1904  
  1905  func (r *SearchRegexpRes) SetOffline() {
  1906  	r.Offline = true
  1907  }
  1908  
  1909  func (r *SearchInboxRes) SetOffline() {
  1910  	r.Offline = true
  1911  }
  1912  
  1913  func (t TyperInfo) String() string {
  1914  	return fmt.Sprintf("typer(u:%s d:%s)", t.Username, t.DeviceID)
  1915  }
  1916  
  1917  func (o TLFConvOrdinal) Int() int {
  1918  	return int(o)
  1919  }
  1920  
  1921  func (o TLFConvOrdinal) IsFirst() bool {
  1922  	return o.Int() == 1
  1923  }
  1924  
  1925  func MakeEmptyUnreadUpdate(convID ConversationID) UnreadUpdate {
  1926  	counts := make(map[keybase1.DeviceType]int)
  1927  	counts[keybase1.DeviceType_DESKTOP] = 0
  1928  	counts[keybase1.DeviceType_MOBILE] = 0
  1929  	return UnreadUpdate{
  1930  		ConvID:                  convID,
  1931  		UnreadMessages:          0,
  1932  		UnreadNotifyingMessages: counts,
  1933  	}
  1934  }
  1935  
  1936  func (u UnreadUpdate) String() string {
  1937  	return fmt.Sprintf("[d:%v c:%s u:%d nd:%d nm:%d]", u.Diff, u.ConvID, u.UnreadMessages,
  1938  		u.UnreadNotifyingMessages[keybase1.DeviceType_DESKTOP],
  1939  		u.UnreadNotifyingMessages[keybase1.DeviceType_MOBILE])
  1940  }
  1941  
  1942  func (s TopicNameState) Bytes() []byte {
  1943  	return []byte(s)
  1944  }
  1945  
  1946  func (s TopicNameState) Eq(o TopicNameState) bool {
  1947  	return bytes.Equal(s.Bytes(), o.Bytes())
  1948  }
  1949  
  1950  func (i InboxUIItem) GetConvID() ConversationID {
  1951  	bConvID, _ := hex.DecodeString(i.ConvID.String())
  1952  	return ConversationID(bConvID)
  1953  }
  1954  
  1955  type ByConversationMemberStatus []ConversationMemberStatus
  1956  
  1957  func (m ByConversationMemberStatus) Len() int           { return len(m) }
  1958  func (m ByConversationMemberStatus) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
  1959  func (m ByConversationMemberStatus) Less(i, j int) bool { return m[i] > m[j] }
  1960  
  1961  func AllConversationMemberStatuses() (res []ConversationMemberStatus) {
  1962  	for status := range ConversationMemberStatusRevMap {
  1963  		res = append(res, status)
  1964  	}
  1965  	sort.Sort(ByConversationMemberStatus(res))
  1966  	return res
  1967  }
  1968  
  1969  type ByConversationExistence []ConversationExistence
  1970  
  1971  func (m ByConversationExistence) Len() int           { return len(m) }
  1972  func (m ByConversationExistence) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
  1973  func (m ByConversationExistence) Less(i, j int) bool { return m[i] > m[j] }
  1974  
  1975  func AllConversationExistences() (res []ConversationExistence) {
  1976  	for existence := range ConversationExistenceRevMap {
  1977  		res = append(res, existence)
  1978  	}
  1979  	sort.Sort(ByConversationExistence(res))
  1980  	return res
  1981  }
  1982  
  1983  func (v InboxVers) ToConvVers() ConversationVers {
  1984  	return ConversationVers(v)
  1985  }
  1986  
  1987  func (p ConversationIDMessageIDPairs) Contains(convID ConversationID) (MessageID, bool) {
  1988  	for _, c := range p.Pairs {
  1989  		if c.ConvID.Eq(convID) {
  1990  			return c.MsgID, true
  1991  		}
  1992  	}
  1993  	return MessageID(0), false
  1994  }
  1995  
  1996  func (c ConversationMemberStatus) ToGregorDBString() (string, error) {
  1997  	s, ok := ConversationMemberStatusRevMap[c]
  1998  	if !ok {
  1999  		return "", fmt.Errorf("unrecoginzed ConversationMemberStatus: %v", c)
  2000  	}
  2001  	return strings.ToLower(s), nil
  2002  }
  2003  
  2004  func (c ConversationMemberStatus) ToGregorDBStringAssert() string {
  2005  	s, err := c.ToGregorDBString()
  2006  	if err != nil {
  2007  		panic(err)
  2008  	}
  2009  	return s
  2010  }
  2011  
  2012  func humanizeDuration(duration time.Duration) string {
  2013  	var value float64
  2014  	var unit string
  2015  	if int(duration.Hours()) >= 24 {
  2016  		value = duration.Hours() / 24
  2017  		unit = "day"
  2018  	} else if int(duration.Hours()) >= 1 {
  2019  		value = duration.Hours()
  2020  		unit = "hour"
  2021  	} else if int(duration.Minutes()) >= 1 {
  2022  		value = duration.Minutes()
  2023  		unit = "minute"
  2024  	} else if int(duration.Seconds()) >= 1 {
  2025  		value = duration.Seconds()
  2026  		unit = "second"
  2027  	} else {
  2028  		return ""
  2029  	}
  2030  	if int(value) > 1 {
  2031  		unit += "s"
  2032  	}
  2033  	return fmt.Sprintf("%.0f %s", value, unit)
  2034  }
  2035  
  2036  func (p RetentionPolicy) Eq(o RetentionPolicy) bool {
  2037  	typ1, err := p.Typ()
  2038  	if err != nil {
  2039  		return false
  2040  	}
  2041  
  2042  	typ2, err := o.Typ()
  2043  	if err != nil {
  2044  		return false
  2045  	}
  2046  	if typ1 != typ2 {
  2047  		return false
  2048  	}
  2049  	switch typ1 {
  2050  	case RetentionPolicyType_NONE:
  2051  		return true
  2052  	case RetentionPolicyType_RETAIN:
  2053  		return p.Retain() == o.Retain()
  2054  	case RetentionPolicyType_EXPIRE:
  2055  		return p.Expire() == o.Expire()
  2056  	case RetentionPolicyType_INHERIT:
  2057  		return p.Inherit() == o.Inherit()
  2058  	case RetentionPolicyType_EPHEMERAL:
  2059  		return p.Ephemeral() == o.Ephemeral()
  2060  	default:
  2061  		return false
  2062  	}
  2063  }
  2064  
  2065  func (p RetentionPolicy) HumanSummary() (summary string) {
  2066  	typ, err := p.Typ()
  2067  	if err != nil {
  2068  		return ""
  2069  	}
  2070  
  2071  	switch typ {
  2072  	case RetentionPolicyType_NONE, RetentionPolicyType_RETAIN:
  2073  		summary = "be retained indefinitely"
  2074  	case RetentionPolicyType_EXPIRE:
  2075  		duration := humanizeDuration(p.Expire().Age.ToDuration())
  2076  		if duration != "" {
  2077  			summary = fmt.Sprintf("expire after %s", duration)
  2078  		}
  2079  	case RetentionPolicyType_EPHEMERAL:
  2080  		duration := humanizeDuration(p.Ephemeral().Age.ToDuration())
  2081  		if duration != "" {
  2082  			summary = fmt.Sprintf("explode after %s by default", duration)
  2083  		}
  2084  	}
  2085  	if summary != "" {
  2086  		summary = fmt.Sprintf("Messages will %s", summary)
  2087  	}
  2088  	return summary
  2089  }
  2090  
  2091  func (p RetentionPolicy) Summary() string {
  2092  	typ, err := p.Typ()
  2093  	if err != nil {
  2094  		return "{variant error}"
  2095  	}
  2096  	switch typ {
  2097  	case RetentionPolicyType_EXPIRE:
  2098  		return fmt.Sprintf("{%v age:%v}", typ, p.Expire().Age.ToDuration())
  2099  	case RetentionPolicyType_EPHEMERAL:
  2100  		return fmt.Sprintf("{%v age:%v}", typ, p.Ephemeral().Age.ToDuration())
  2101  	default:
  2102  		return fmt.Sprintf("{%v}", typ)
  2103  	}
  2104  }
  2105  
  2106  func TeamIDToTLFID(teamID keybase1.TeamID) (TLFID, error) {
  2107  	return MakeTLFID(teamID.String())
  2108  }
  2109  
  2110  func (r *NonblockFetchRes) GetRateLimit() []RateLimit {
  2111  	return r.RateLimits
  2112  }
  2113  
  2114  func (r *NonblockFetchRes) SetRateLimits(rl []RateLimit) {
  2115  	r.RateLimits = rl
  2116  }
  2117  
  2118  func (r *UnreadlineRes) GetRateLimit() []RateLimit {
  2119  	return r.RateLimits
  2120  }
  2121  
  2122  func (r *UnreadlineRes) SetRateLimits(rl []RateLimit) {
  2123  	r.RateLimits = rl
  2124  }
  2125  
  2126  func (r *MarkAsReadLocalRes) GetRateLimit() []RateLimit {
  2127  	return r.RateLimits
  2128  }
  2129  
  2130  func (r *MarkAsReadLocalRes) SetRateLimits(rl []RateLimit) {
  2131  	r.RateLimits = rl
  2132  }
  2133  
  2134  func (r *MarkTLFAsReadLocalRes) GetRateLimit() (res []RateLimit) {
  2135  	return r.RateLimits
  2136  }
  2137  
  2138  func (r *MarkTLFAsReadLocalRes) SetRateLimits(rl []RateLimit) {
  2139  	r.RateLimits = rl
  2140  }
  2141  
  2142  func (r *GetInboxAndUnboxLocalRes) GetRateLimit() []RateLimit {
  2143  	return r.RateLimits
  2144  }
  2145  
  2146  func (r *GetInboxAndUnboxLocalRes) SetRateLimits(rl []RateLimit) {
  2147  	r.RateLimits = rl
  2148  }
  2149  
  2150  func (r *GetAllResetConvMembersRes) GetRateLimit() []RateLimit {
  2151  	return r.RateLimits
  2152  }
  2153  
  2154  func (r *GetAllResetConvMembersRes) SetRateLimits(rl []RateLimit) {
  2155  	r.RateLimits = rl
  2156  }
  2157  
  2158  func (r *LoadFlipRes) GetRateLimit() []RateLimit {
  2159  	return r.RateLimits
  2160  }
  2161  
  2162  func (r *LoadFlipRes) SetRateLimits(rl []RateLimit) {
  2163  	r.RateLimits = rl
  2164  }
  2165  
  2166  func (r *GetInboxAndUnboxUILocalRes) GetRateLimit() []RateLimit {
  2167  	return r.RateLimits
  2168  }
  2169  
  2170  func (r *GetInboxAndUnboxUILocalRes) SetRateLimits(rl []RateLimit) {
  2171  	r.RateLimits = rl
  2172  }
  2173  
  2174  func (r *GetThreadLocalRes) GetRateLimit() []RateLimit {
  2175  	return r.RateLimits
  2176  }
  2177  
  2178  func (r *GetThreadLocalRes) SetRateLimits(rl []RateLimit) {
  2179  	r.RateLimits = rl
  2180  }
  2181  
  2182  func (r *NewConversationLocalRes) GetRateLimit() []RateLimit {
  2183  	return r.RateLimits
  2184  }
  2185  
  2186  func (r *NewConversationLocalRes) SetRateLimits(rl []RateLimit) {
  2187  	r.RateLimits = rl
  2188  }
  2189  
  2190  func (r *GetInboxSummaryForCLILocalRes) GetRateLimit() []RateLimit {
  2191  	return r.RateLimits
  2192  }
  2193  
  2194  func (r *GetInboxSummaryForCLILocalRes) SetRateLimits(rl []RateLimit) {
  2195  	r.RateLimits = rl
  2196  }
  2197  
  2198  func (r *GetMessagesLocalRes) GetRateLimit() []RateLimit {
  2199  	return r.RateLimits
  2200  }
  2201  
  2202  func (r *GetMessagesLocalRes) SetRateLimits(rl []RateLimit) {
  2203  	r.RateLimits = rl
  2204  }
  2205  
  2206  func (r *GetNextAttachmentMessageLocalRes) GetRateLimit() []RateLimit {
  2207  	return r.RateLimits
  2208  }
  2209  
  2210  func (r *GetNextAttachmentMessageLocalRes) SetRateLimits(rl []RateLimit) {
  2211  	r.RateLimits = rl
  2212  }
  2213  
  2214  func (r *SetConversationStatusLocalRes) GetRateLimit() []RateLimit {
  2215  	return r.RateLimits
  2216  }
  2217  
  2218  func (r *SetConversationStatusLocalRes) SetRateLimits(rl []RateLimit) {
  2219  	r.RateLimits = rl
  2220  }
  2221  
  2222  func (r *PostLocalRes) GetRateLimit() []RateLimit {
  2223  	return r.RateLimits
  2224  }
  2225  
  2226  func (r *PostLocalRes) SetRateLimits(rl []RateLimit) {
  2227  	r.RateLimits = rl
  2228  }
  2229  
  2230  func (r *GetConversationForCLILocalRes) GetRateLimit() []RateLimit {
  2231  	return r.RateLimits
  2232  }
  2233  
  2234  func (r *GetConversationForCLILocalRes) SetRateLimits(rl []RateLimit) {
  2235  	r.RateLimits = rl
  2236  }
  2237  
  2238  func (r *PostLocalNonblockRes) GetRateLimit() []RateLimit {
  2239  	return r.RateLimits
  2240  }
  2241  
  2242  func (r *PostLocalNonblockRes) SetRateLimits(rl []RateLimit) {
  2243  	r.RateLimits = rl
  2244  }
  2245  
  2246  func (r *DownloadAttachmentLocalRes) GetRateLimit() []RateLimit {
  2247  	return r.RateLimits
  2248  }
  2249  
  2250  func (r *DownloadAttachmentLocalRes) SetRateLimits(rl []RateLimit) {
  2251  	r.RateLimits = rl
  2252  }
  2253  
  2254  func (r *DownloadFileAttachmentLocalRes) GetRateLimit() []RateLimit {
  2255  	return r.RateLimits
  2256  }
  2257  
  2258  func (r *DownloadFileAttachmentLocalRes) SetRateLimits(rl []RateLimit) {
  2259  	r.RateLimits = rl
  2260  }
  2261  
  2262  func (r *FindConversationsLocalRes) GetRateLimit() []RateLimit {
  2263  	return r.RateLimits
  2264  }
  2265  
  2266  func (r *FindConversationsLocalRes) SetRateLimits(rl []RateLimit) {
  2267  	r.RateLimits = rl
  2268  }
  2269  
  2270  func (r *JoinLeaveConversationLocalRes) GetRateLimit() []RateLimit {
  2271  	return r.RateLimits
  2272  }
  2273  
  2274  func (r *JoinLeaveConversationLocalRes) SetRateLimits(rl []RateLimit) {
  2275  	r.RateLimits = rl
  2276  }
  2277  
  2278  func (r *PreviewConversationLocalRes) GetRateLimit() []RateLimit {
  2279  	return r.RateLimits
  2280  }
  2281  
  2282  func (r *PreviewConversationLocalRes) SetRateLimits(rl []RateLimit) {
  2283  	r.RateLimits = rl
  2284  }
  2285  
  2286  func (r *DeleteConversationLocalRes) GetRateLimit() []RateLimit {
  2287  	return r.RateLimits
  2288  }
  2289  
  2290  func (r *DeleteConversationLocalRes) SetRateLimits(rl []RateLimit) {
  2291  	r.RateLimits = rl
  2292  }
  2293  
  2294  func (r *RemoveFromConversationLocalRes) GetRateLimit() []RateLimit {
  2295  	return r.RateLimits
  2296  }
  2297  
  2298  func (r *RemoveFromConversationLocalRes) SetRateLimits(rl []RateLimit) {
  2299  	r.RateLimits = rl
  2300  }
  2301  
  2302  func (r *GetTLFConversationsLocalRes) GetRateLimit() []RateLimit {
  2303  	return r.RateLimits
  2304  }
  2305  
  2306  func (r *GetTLFConversationsLocalRes) SetRateLimits(rl []RateLimit) {
  2307  	r.RateLimits = rl
  2308  }
  2309  
  2310  func (r *GetChannelMembershipsLocalRes) GetRateLimit() []RateLimit {
  2311  	return r.RateLimits
  2312  }
  2313  
  2314  func (r *GetChannelMembershipsLocalRes) SetRateLimits(rl []RateLimit) {
  2315  	r.RateLimits = rl
  2316  }
  2317  
  2318  func (r *GetMutualTeamsLocalRes) GetRateLimit() []RateLimit {
  2319  	return r.RateLimits
  2320  }
  2321  
  2322  func (r *GetMutualTeamsLocalRes) SetRateLimits(rl []RateLimit) {
  2323  	r.RateLimits = rl
  2324  }
  2325  
  2326  func (r *SetAppNotificationSettingsLocalRes) GetRateLimit() []RateLimit {
  2327  	return r.RateLimits
  2328  }
  2329  
  2330  func (r *SetAppNotificationSettingsLocalRes) SetRateLimits(rl []RateLimit) {
  2331  	r.RateLimits = rl
  2332  }
  2333  
  2334  func (r *SearchRegexpRes) GetRateLimit() []RateLimit {
  2335  	return r.RateLimits
  2336  }
  2337  
  2338  func (r *SearchRegexpRes) SetRateLimits(rl []RateLimit) {
  2339  	r.RateLimits = rl
  2340  }
  2341  
  2342  func (r *SearchInboxRes) GetRateLimit() []RateLimit {
  2343  	return r.RateLimits
  2344  }
  2345  
  2346  func (r *SearchInboxRes) SetRateLimits(rl []RateLimit) {
  2347  	r.RateLimits = rl
  2348  }
  2349  
  2350  func (r *GetInboxRemoteRes) GetRateLimit() (res []RateLimit) {
  2351  	if r.RateLimit != nil {
  2352  		res = []RateLimit{*r.RateLimit}
  2353  	}
  2354  	return res
  2355  }
  2356  
  2357  func (r *GetInboxRemoteRes) SetRateLimits(rl []RateLimit) {
  2358  	if len(rl) > 0 {
  2359  		r.RateLimit = &rl[0]
  2360  	}
  2361  }
  2362  
  2363  func (r *GetInboxByTLFIDRemoteRes) GetRateLimit() (res []RateLimit) {
  2364  	if r.RateLimit != nil {
  2365  		res = []RateLimit{*r.RateLimit}
  2366  	}
  2367  	return res
  2368  }
  2369  
  2370  func (r *GetInboxByTLFIDRemoteRes) SetRateLimits(rl []RateLimit) {
  2371  	if len(rl) > 0 {
  2372  		r.RateLimit = &rl[0]
  2373  	}
  2374  }
  2375  
  2376  func (r *GetThreadRemoteRes) GetRateLimit() (res []RateLimit) {
  2377  	if r.RateLimit != nil {
  2378  		res = []RateLimit{*r.RateLimit}
  2379  	}
  2380  	return res
  2381  }
  2382  
  2383  func (r *GetThreadRemoteRes) SetRateLimits(rl []RateLimit) {
  2384  	if len(rl) > 0 {
  2385  		r.RateLimit = &rl[0]
  2386  	}
  2387  }
  2388  
  2389  func (r *GetConversationMetadataRemoteRes) GetRateLimit() (res []RateLimit) {
  2390  	if r.RateLimit != nil {
  2391  		res = []RateLimit{*r.RateLimit}
  2392  	}
  2393  	return res
  2394  }
  2395  
  2396  func (r *GetConversationMetadataRemoteRes) SetRateLimits(rl []RateLimit) {
  2397  	if len(rl) > 0 {
  2398  		r.RateLimit = &rl[0]
  2399  	}
  2400  }
  2401  
  2402  func (r *PostRemoteRes) GetRateLimit() (res []RateLimit) {
  2403  	if r.RateLimit != nil {
  2404  		res = []RateLimit{*r.RateLimit}
  2405  	}
  2406  	return res
  2407  }
  2408  
  2409  func (r *PostRemoteRes) SetRateLimits(rl []RateLimit) {
  2410  	if len(rl) > 0 {
  2411  		r.RateLimit = &rl[0]
  2412  	}
  2413  }
  2414  
  2415  func (r *NewConversationRemoteRes) GetRateLimit() (res []RateLimit) {
  2416  	if r.RateLimit != nil {
  2417  		res = []RateLimit{*r.RateLimit}
  2418  	}
  2419  	return res
  2420  }
  2421  
  2422  func (r *NewConversationRemoteRes) SetRateLimits(rl []RateLimit) {
  2423  	if len(rl) > 0 {
  2424  		r.RateLimit = &rl[0]
  2425  	}
  2426  }
  2427  
  2428  func (r *GetMessagesRemoteRes) GetRateLimit() (res []RateLimit) {
  2429  	if r.RateLimit != nil {
  2430  		res = []RateLimit{*r.RateLimit}
  2431  	}
  2432  	return res
  2433  }
  2434  
  2435  func (r *GetMessagesRemoteRes) SetRateLimits(rl []RateLimit) {
  2436  	if len(rl) > 0 {
  2437  		r.RateLimit = &rl[0]
  2438  	}
  2439  }
  2440  
  2441  func (r *MarkAsReadRes) GetRateLimit() (res []RateLimit) {
  2442  	if r.RateLimit != nil {
  2443  		res = []RateLimit{*r.RateLimit}
  2444  	}
  2445  	return res
  2446  }
  2447  
  2448  func (r *MarkAsReadRes) SetRateLimits(rl []RateLimit) {
  2449  	if len(rl) > 0 {
  2450  		r.RateLimit = &rl[0]
  2451  	}
  2452  }
  2453  
  2454  func (r *SetConversationStatusRes) GetRateLimit() (res []RateLimit) {
  2455  	if r.RateLimit != nil {
  2456  		res = []RateLimit{*r.RateLimit}
  2457  	}
  2458  	return res
  2459  }
  2460  
  2461  func (r *SetConversationStatusRes) SetRateLimits(rl []RateLimit) {
  2462  	if len(rl) > 0 {
  2463  		r.RateLimit = &rl[0]
  2464  	}
  2465  }
  2466  
  2467  func (r *GetPublicConversationsRes) GetRateLimit() (res []RateLimit) {
  2468  	if r.RateLimit != nil {
  2469  		res = []RateLimit{*r.RateLimit}
  2470  	}
  2471  	return res
  2472  }
  2473  
  2474  func (r *GetPublicConversationsRes) SetRateLimits(rl []RateLimit) {
  2475  	if len(rl) > 0 {
  2476  		r.RateLimit = &rl[0]
  2477  	}
  2478  }
  2479  
  2480  func (r *JoinLeaveConversationRemoteRes) GetRateLimit() (res []RateLimit) {
  2481  	if r.RateLimit != nil {
  2482  		res = []RateLimit{*r.RateLimit}
  2483  	}
  2484  	return res
  2485  }
  2486  
  2487  func (r *JoinLeaveConversationRemoteRes) SetRateLimits(rl []RateLimit) {
  2488  	if len(rl) > 0 {
  2489  		r.RateLimit = &rl[0]
  2490  	}
  2491  }
  2492  
  2493  func (r *DeleteConversationRemoteRes) GetRateLimit() (res []RateLimit) {
  2494  	if r.RateLimit != nil {
  2495  		res = []RateLimit{*r.RateLimit}
  2496  	}
  2497  	return res
  2498  }
  2499  
  2500  func (r *DeleteConversationRemoteRes) SetRateLimits(rl []RateLimit) {
  2501  	if len(rl) > 0 {
  2502  		r.RateLimit = &rl[0]
  2503  	}
  2504  }
  2505  
  2506  func (r *RemoveFromConversationRemoteRes) GetRateLimit() (res []RateLimit) {
  2507  	if r.RateLimit != nil {
  2508  		res = []RateLimit{*r.RateLimit}
  2509  	}
  2510  	return res
  2511  }
  2512  
  2513  func (r *RemoveFromConversationRemoteRes) SetRateLimits(rl []RateLimit) {
  2514  	if len(rl) > 0 {
  2515  		r.RateLimit = &rl[0]
  2516  	}
  2517  }
  2518  
  2519  func (r *GetMessageBeforeRes) GetRateLimit() (res []RateLimit) {
  2520  	if r.RateLimit != nil {
  2521  		res = []RateLimit{*r.RateLimit}
  2522  	}
  2523  	return res
  2524  }
  2525  
  2526  func (r *GetMessageBeforeRes) SetRateLimits(rl []RateLimit) {
  2527  	if len(rl) > 0 {
  2528  		r.RateLimit = &rl[0]
  2529  	}
  2530  }
  2531  
  2532  func (r *GetTLFConversationsRes) GetRateLimit() (res []RateLimit) {
  2533  	if r.RateLimit != nil {
  2534  		res = []RateLimit{*r.RateLimit}
  2535  	}
  2536  	return res
  2537  }
  2538  
  2539  func (r *GetTLFConversationsRes) SetRateLimits(rl []RateLimit) {
  2540  	if len(rl) > 0 {
  2541  		r.RateLimit = &rl[0]
  2542  	}
  2543  }
  2544  
  2545  func (r *SetAppNotificationSettingsRes) GetRateLimit() (res []RateLimit) {
  2546  	if r.RateLimit != nil {
  2547  		res = []RateLimit{*r.RateLimit}
  2548  	}
  2549  	return res
  2550  }
  2551  
  2552  func (r *SetAppNotificationSettingsRes) SetRateLimits(rl []RateLimit) {
  2553  	if len(rl) > 0 {
  2554  		r.RateLimit = &rl[0]
  2555  	}
  2556  }
  2557  
  2558  func (r *SetRetentionRes) GetRateLimit() (res []RateLimit) {
  2559  	if r.RateLimit != nil {
  2560  		res = []RateLimit{*r.RateLimit}
  2561  	}
  2562  	return res
  2563  }
  2564  
  2565  func (r *SetRetentionRes) SetRateLimits(rl []RateLimit) {
  2566  	if len(rl) > 0 {
  2567  		r.RateLimit = &rl[0]
  2568  	}
  2569  }
  2570  
  2571  func (r *LoadGalleryRes) GetRateLimit() []RateLimit {
  2572  	return r.RateLimits
  2573  }
  2574  
  2575  func (r *LoadGalleryRes) SetRateLimits(rl []RateLimit) {
  2576  	r.RateLimits = rl
  2577  }
  2578  
  2579  func (r *ListBotCommandsLocalRes) GetRateLimit() []RateLimit {
  2580  	return r.RateLimits
  2581  }
  2582  
  2583  func (r *ListBotCommandsLocalRes) SetRateLimits(rl []RateLimit) {
  2584  	r.RateLimits = rl
  2585  }
  2586  
  2587  func (r *PinMessageRes) GetRateLimit() []RateLimit {
  2588  	return r.RateLimits
  2589  }
  2590  
  2591  func (r *PinMessageRes) SetRateLimits(rl []RateLimit) {
  2592  	r.RateLimits = rl
  2593  }
  2594  
  2595  func (r *ClearBotCommandsLocalRes) GetRateLimit() []RateLimit {
  2596  	return r.RateLimits
  2597  }
  2598  
  2599  func (r *ClearBotCommandsLocalRes) SetRateLimits(rl []RateLimit) {
  2600  	r.RateLimits = rl
  2601  }
  2602  
  2603  func (r *ClearBotCommandsRes) GetRateLimit() (res []RateLimit) {
  2604  	if r.RateLimit != nil {
  2605  		res = []RateLimit{*r.RateLimit}
  2606  	}
  2607  	return res
  2608  }
  2609  
  2610  func (r *ClearBotCommandsRes) SetRateLimits(rl []RateLimit) {
  2611  	if len(rl) > 0 {
  2612  		r.RateLimit = &rl[0]
  2613  	}
  2614  }
  2615  
  2616  func (r *AdvertiseBotCommandsLocalRes) GetRateLimit() []RateLimit {
  2617  	return r.RateLimits
  2618  }
  2619  
  2620  func (r *AdvertiseBotCommandsLocalRes) SetRateLimits(rl []RateLimit) {
  2621  	r.RateLimits = rl
  2622  }
  2623  
  2624  func (r *AdvertiseBotCommandsRes) GetRateLimit() (res []RateLimit) {
  2625  	if r.RateLimit != nil {
  2626  		res = []RateLimit{*r.RateLimit}
  2627  	}
  2628  	return res
  2629  }
  2630  
  2631  func (r *AdvertiseBotCommandsRes) SetRateLimits(rl []RateLimit) {
  2632  	if len(rl) > 0 {
  2633  		r.RateLimit = &rl[0]
  2634  	}
  2635  }
  2636  
  2637  func (r *GetBotInfoRes) GetRateLimit() (res []RateLimit) {
  2638  	if r.RateLimit != nil {
  2639  		res = []RateLimit{*r.RateLimit}
  2640  	}
  2641  	return res
  2642  }
  2643  
  2644  func (r *GetBotInfoRes) SetRateLimits(rl []RateLimit) {
  2645  	if len(rl) > 0 {
  2646  		r.RateLimit = &rl[0]
  2647  	}
  2648  }
  2649  
  2650  func (r *NewConversationsLocalRes) GetRateLimit() (res []RateLimit) {
  2651  	return r.RateLimits
  2652  }
  2653  
  2654  func (r *NewConversationsLocalRes) SetRateLimits(rl []RateLimit) {
  2655  	r.RateLimits = rl
  2656  }
  2657  
  2658  func (r *GetDefaultTeamChannelsLocalRes) GetRateLimit() (res []RateLimit) {
  2659  	if r.RateLimit != nil {
  2660  		res = []RateLimit{*r.RateLimit}
  2661  	}
  2662  	return res
  2663  }
  2664  
  2665  func (r *GetDefaultTeamChannelsLocalRes) SetRateLimits(rl []RateLimit) {
  2666  	if len(rl) > 0 {
  2667  		r.RateLimit = &rl[0]
  2668  	}
  2669  }
  2670  
  2671  func (r *SetDefaultTeamChannelsLocalRes) GetRateLimit() (res []RateLimit) {
  2672  	if r.RateLimit != nil {
  2673  		res = []RateLimit{*r.RateLimit}
  2674  	}
  2675  	return res
  2676  }
  2677  
  2678  func (r *SetDefaultTeamChannelsLocalRes) SetRateLimits(rl []RateLimit) {
  2679  	if len(rl) > 0 {
  2680  		r.RateLimit = &rl[0]
  2681  	}
  2682  }
  2683  
  2684  func (r *AddEmojiRes) GetRateLimit() (res []RateLimit) {
  2685  	if r.RateLimit != nil {
  2686  		res = []RateLimit{*r.RateLimit}
  2687  	}
  2688  	return res
  2689  }
  2690  
  2691  func (r *AddEmojiRes) SetRateLimits(rl []RateLimit) {
  2692  	if len(rl) > 0 {
  2693  		r.RateLimit = &rl[0]
  2694  	}
  2695  }
  2696  
  2697  func (r *AddEmojisRes) GetRateLimit() (res []RateLimit) {
  2698  	if r.RateLimit != nil {
  2699  		res = []RateLimit{*r.RateLimit}
  2700  	}
  2701  	return res
  2702  }
  2703  
  2704  func (r *AddEmojisRes) SetRateLimits(rl []RateLimit) {
  2705  	if len(rl) > 0 {
  2706  		r.RateLimit = &rl[0]
  2707  	}
  2708  }
  2709  
  2710  func (r *AddEmojiAliasRes) GetRateLimit() (res []RateLimit) {
  2711  	if r.RateLimit != nil {
  2712  		res = []RateLimit{*r.RateLimit}
  2713  	}
  2714  	return res
  2715  }
  2716  
  2717  func (r *AddEmojiAliasRes) SetRateLimits(rl []RateLimit) {
  2718  	if len(rl) > 0 {
  2719  		r.RateLimit = &rl[0]
  2720  	}
  2721  }
  2722  
  2723  func (r *RemoveEmojiRes) GetRateLimit() (res []RateLimit) {
  2724  	if r.RateLimit != nil {
  2725  		res = []RateLimit{*r.RateLimit}
  2726  	}
  2727  	return res
  2728  }
  2729  
  2730  func (r *RemoveEmojiRes) SetRateLimits(rl []RateLimit) {
  2731  	if len(rl) > 0 {
  2732  		r.RateLimit = &rl[0]
  2733  	}
  2734  }
  2735  
  2736  func (r *UserEmojiRes) GetRateLimit() (res []RateLimit) {
  2737  	if r.RateLimit != nil {
  2738  		res = []RateLimit{*r.RateLimit}
  2739  	}
  2740  	return res
  2741  }
  2742  
  2743  func (r *UserEmojiRes) SetRateLimits(rl []RateLimit) {
  2744  	if len(rl) > 0 {
  2745  		r.RateLimit = &rl[0]
  2746  	}
  2747  }
  2748  
  2749  func (i EphemeralPurgeInfo) IsNil() bool {
  2750  	return !i.IsActive && i.NextPurgeTime == 0 && i.MinUnexplodedID <= 1
  2751  }
  2752  
  2753  func (i EphemeralPurgeInfo) String() string {
  2754  	return fmt.Sprintf("EphemeralPurgeInfo{ ConvID: %v, IsActive: %v, NextPurgeTime: %v, MinUnexplodedID: %v }",
  2755  		i.ConvID, i.IsActive, i.NextPurgeTime.Time(), i.MinUnexplodedID)
  2756  }
  2757  
  2758  func (i EphemeralPurgeInfo) Eq(o EphemeralPurgeInfo) bool {
  2759  	return (i.IsActive == o.IsActive &&
  2760  		i.MinUnexplodedID == o.MinUnexplodedID &&
  2761  		i.NextPurgeTime == o.NextPurgeTime &&
  2762  		i.ConvID.Eq(o.ConvID))
  2763  }
  2764  
  2765  func (r ReactionMap) HasReactionFromUser(reactionText, username string) (found bool, reactionMsgID MessageID) {
  2766  	reactions, ok := r.Reactions[reactionText]
  2767  	if !ok {
  2768  		return false, 0
  2769  	}
  2770  	reaction, ok := reactions[username]
  2771  	return ok, reaction.ReactionMsgID
  2772  }
  2773  
  2774  func (r MessageReaction) Eq(o MessageReaction) bool {
  2775  	return r.Body == o.Body && r.MessageID == o.MessageID
  2776  }
  2777  
  2778  func (i *ConversationMinWriterRoleInfoLocal) String() string {
  2779  	if i == nil {
  2780  		return "Minimum writer role for this conversation is not set."
  2781  	}
  2782  	changedBySuffix := "."
  2783  	if i.ChangedBy != "" {
  2784  		changedBySuffix = fmt.Sprintf(", last set by %v.", i.ChangedBy)
  2785  	}
  2786  	return fmt.Sprintf("Minimum writer role for this conversation is %v%v", i.Role, changedBySuffix)
  2787  }
  2788  
  2789  func (s *ConversationSettings) IsNil() bool {
  2790  	return s == nil || s.MinWriterRoleInfo == nil
  2791  }
  2792  
  2793  func (o SearchOpts) Matches(msg MessageUnboxed) bool {
  2794  	if o.SentAfter != 0 && msg.Ctime() < o.SentAfter {
  2795  		return false
  2796  	}
  2797  	if o.SentBefore != 0 && msg.Ctime() > o.SentBefore {
  2798  		return false
  2799  	}
  2800  	if o.SentBy != "" && msg.SenderUsername() != o.SentBy {
  2801  		return false
  2802  	}
  2803  	// Check if the user was @mentioned or there was a @here/@channel.
  2804  	if o.SentTo != "" {
  2805  		if o.MatchMentions {
  2806  			switch msg.ChannelMention() {
  2807  			case ChannelMention_ALL, ChannelMention_HERE:
  2808  				return true
  2809  			}
  2810  		}
  2811  		for _, username := range msg.AtMentionUsernames() {
  2812  			if o.SentTo == username {
  2813  				return true
  2814  			}
  2815  		}
  2816  		return false
  2817  	}
  2818  	return true
  2819  }
  2820  
  2821  func (a MessageAttachment) GetTitle() string {
  2822  	title := a.Object.Title
  2823  	if title == "" {
  2824  		title = filepath.Base(a.Object.Filename)
  2825  	}
  2826  	return title
  2827  }
  2828  
  2829  func (u MessageUnfurl) SearchableText() string {
  2830  	typ, err := u.Unfurl.Unfurl.UnfurlType()
  2831  	if err != nil {
  2832  		return ""
  2833  	}
  2834  	switch typ {
  2835  	case UnfurlType_GENERIC:
  2836  		generic := u.Unfurl.Unfurl.Generic()
  2837  		res := generic.Title
  2838  		if generic.Description != nil {
  2839  			res += " " + *generic.Description
  2840  		}
  2841  		return res
  2842  	}
  2843  	return ""
  2844  }
  2845  
  2846  func (h *ChatSearchInboxHit) Size() int {
  2847  	if h == nil {
  2848  		return 0
  2849  	}
  2850  	return len(h.Hits)
  2851  }
  2852  
  2853  func (u UnfurlRaw) GetUrl() string {
  2854  	typ, err := u.UnfurlType()
  2855  	if err != nil {
  2856  		return ""
  2857  	}
  2858  	switch typ {
  2859  	case UnfurlType_GENERIC:
  2860  		return u.Generic().Url
  2861  	case UnfurlType_GIPHY:
  2862  		if u.Giphy().ImageUrl != nil {
  2863  			return *u.Giphy().ImageUrl
  2864  		}
  2865  	}
  2866  	return ""
  2867  }
  2868  
  2869  func (u UnfurlRaw) UnsafeDebugString() string {
  2870  	typ, err := u.UnfurlType()
  2871  	if err != nil {
  2872  		return "<error>"
  2873  	}
  2874  	switch typ {
  2875  	case UnfurlType_GENERIC:
  2876  		return u.Generic().UnsafeDebugString()
  2877  	case UnfurlType_GIPHY:
  2878  		return u.Giphy().UnsafeDebugString()
  2879  	}
  2880  	return "<unknown>"
  2881  }
  2882  
  2883  func yieldStr(s *string) string {
  2884  	if s == nil {
  2885  		return ""
  2886  	}
  2887  	return *s
  2888  }
  2889  
  2890  func (g UnfurlGenericRaw) UnsafeDebugString() string {
  2891  
  2892  	publishTime := ""
  2893  	if g.PublishTime != nil {
  2894  		publishTime = fmt.Sprintf("%v", time.Unix(int64(*g.PublishTime), 0))
  2895  	}
  2896  	return fmt.Sprintf(`Title: %s
  2897  Url: %s
  2898  SiteName: %s
  2899  PublishTime: %s
  2900  Description: %s
  2901  ImageUrl: %s
  2902  Video: %s
  2903  FaviconUrl: %s`, g.Title, g.Url, g.SiteName, publishTime, yieldStr(g.Description),
  2904  		yieldStr(g.ImageUrl), g.Video, yieldStr(g.FaviconUrl))
  2905  }
  2906  
  2907  func (g UnfurlGiphyRaw) UnsafeDebugString() string {
  2908  
  2909  	return fmt.Sprintf(`GIPHY SPECIAL
  2910  FaviconUrl: %s
  2911  ImageUrl: %s
  2912  Video: %s`, yieldStr(g.FaviconUrl), yieldStr(g.ImageUrl), g.Video)
  2913  }
  2914  
  2915  func (v UnfurlVideo) String() string {
  2916  	return fmt.Sprintf("[url: %s width: %d height: %d mime: %s]", v.Url, v.Width, v.Height, v.MimeType)
  2917  }
  2918  
  2919  func NewUnfurlSettings() UnfurlSettings {
  2920  	return UnfurlSettings{
  2921  		Mode:      UnfurlMode_WHITELISTED,
  2922  		Whitelist: make(map[string]bool),
  2923  	}
  2924  }
  2925  
  2926  func GlobalAppNotificationSettingsSorted() (res []GlobalAppNotificationSetting) {
  2927  	for setting := range GlobalAppNotificationSettingRevMap {
  2928  		if setting.Usage() != "" && setting.FlagName() != "" {
  2929  			res = append(res, setting)
  2930  		}
  2931  	}
  2932  	sort.Slice(res, func(i, j int) bool {
  2933  		return res[i] < res[j]
  2934  	})
  2935  	return res
  2936  }
  2937  
  2938  // Add to `Usage`/`FlagName` for a setting to be usable in the CLI via
  2939  // `keybase notification-settings`
  2940  func (g GlobalAppNotificationSetting) Usage() string {
  2941  	switch g {
  2942  	case GlobalAppNotificationSetting_NEWMESSAGES:
  2943  		return "Show notifications for new messages"
  2944  	case GlobalAppNotificationSetting_PLAINTEXTDESKTOP:
  2945  		return "Show plaintext notifications on desktop devices"
  2946  	case GlobalAppNotificationSetting_PLAINTEXTMOBILE:
  2947  		return "Show plaintext notifications on mobile devices"
  2948  	case GlobalAppNotificationSetting_DEFAULTSOUNDMOBILE:
  2949  		return "Use the default system sound on mobile devices"
  2950  	case GlobalAppNotificationSetting_DISABLETYPING:
  2951  		return "Disable sending/receiving typing notifications"
  2952  	case GlobalAppNotificationSetting_CONVERTHEIC:
  2953  		return "Convert HEIC images to JPEG for chat attachments (macOS and iOS only)"
  2954  	default:
  2955  		return ""
  2956  	}
  2957  }
  2958  
  2959  func (g GlobalAppNotificationSetting) FlagName() string {
  2960  	switch g {
  2961  	case GlobalAppNotificationSetting_NEWMESSAGES:
  2962  		return "new-messages"
  2963  	case GlobalAppNotificationSetting_PLAINTEXTDESKTOP:
  2964  		return "plaintext-desktop"
  2965  	case GlobalAppNotificationSetting_PLAINTEXTMOBILE:
  2966  		return "plaintext-mobile"
  2967  	case GlobalAppNotificationSetting_DEFAULTSOUNDMOBILE:
  2968  		return "default-sound-mobile"
  2969  	case GlobalAppNotificationSetting_DISABLETYPING:
  2970  		return "disable-typing"
  2971  	case GlobalAppNotificationSetting_CONVERTHEIC:
  2972  		return "convert-heic"
  2973  	default:
  2974  		return ""
  2975  	}
  2976  }
  2977  
  2978  func (m MessageSystemChangeRetention) String() string {
  2979  	var appliesTo string
  2980  	switch m.MembersType {
  2981  	case ConversationMembersType_TEAM:
  2982  		if m.IsTeam {
  2983  			appliesTo = "team"
  2984  		} else {
  2985  			appliesTo = "channel"
  2986  		}
  2987  	default:
  2988  		appliesTo = "conversation"
  2989  	}
  2990  	var inheritDescription string
  2991  	if m.IsInherit {
  2992  		inheritDescription = " to inherit from the team policy"
  2993  	}
  2994  
  2995  	format := "%s changed the %s retention policy%s. %s"
  2996  	summary := m.Policy.HumanSummary()
  2997  	return fmt.Sprintf(format, m.User, appliesTo, inheritDescription, summary)
  2998  }
  2999  
  3000  func (m MessageSystemBulkAddToConv) String() string {
  3001  	prefix := "Added %s to the conversation"
  3002  	var suffix string
  3003  	switch len(m.Usernames) {
  3004  	case 0:
  3005  		return ""
  3006  	case 1:
  3007  		suffix = m.Usernames[0]
  3008  	case 2:
  3009  		suffix = fmt.Sprintf("%s and %s", m.Usernames[0], m.Usernames[1])
  3010  	default:
  3011  		suffix = fmt.Sprintf("%s and %d others", m.Usernames[0], len(m.Usernames)-1)
  3012  	}
  3013  	return fmt.Sprintf(prefix, suffix)
  3014  }
  3015  
  3016  func withDeterminer(s string) string {
  3017  	r, size := utf8.DecodeRuneInString(s)
  3018  	if size == 0 || r == utf8.RuneError {
  3019  		return "a " + s
  3020  	}
  3021  	vowels := "aeiou"
  3022  	if strings.Contains(vowels, string(r)) {
  3023  		return "an " + s
  3024  	}
  3025  	return "a " + s
  3026  }
  3027  
  3028  func (m MessageSystem) String() string {
  3029  	typ, err := m.SystemType()
  3030  	if err != nil {
  3031  		return ""
  3032  	}
  3033  	switch typ {
  3034  	case MessageSystemType_ADDEDTOTEAM:
  3035  		output := fmt.Sprintf("Added @%s to the team", m.Addedtoteam().Addee)
  3036  		if role := m.Addedtoteam().Role; role != keybase1.TeamRole_NONE {
  3037  			output += fmt.Sprintf(" as %v", withDeterminer(role.HumanString()))
  3038  		}
  3039  		return output
  3040  	case MessageSystemType_INVITEADDEDTOTEAM:
  3041  		var roleText string
  3042  		if role := m.Inviteaddedtoteam().Role; role != keybase1.TeamRole_NONE {
  3043  			roleText = fmt.Sprintf(" as %v", withDeterminer(role.HumanString()))
  3044  		}
  3045  		output := fmt.Sprintf("Added @%s to the team (invited by @%s%s)",
  3046  			m.Inviteaddedtoteam().Invitee, m.Inviteaddedtoteam().Inviter, roleText)
  3047  		return output
  3048  	case MessageSystemType_COMPLEXTEAM:
  3049  		return fmt.Sprintf("%s is now a 'big' team with multiple channels", m.Complexteam().Team)
  3050  	case MessageSystemType_CREATETEAM:
  3051  		return fmt.Sprintf("@%s created the team %s", m.Createteam().Creator, m.Createteam().Team)
  3052  	case MessageSystemType_GITPUSH:
  3053  		body := m.Gitpush()
  3054  		switch body.PushType {
  3055  		case keybase1.GitPushType_CREATEREPO:
  3056  			return fmt.Sprintf("git @%s created the repo %s", body.Pusher, body.RepoName)
  3057  		case keybase1.GitPushType_RENAMEREPO:
  3058  			return fmt.Sprintf("git @%s changed the name of the repo %s to %s", body.Pusher, body.PreviousRepoName, body.RepoName)
  3059  		default:
  3060  			total := keybase1.TotalNumberOfCommits(body.Refs)
  3061  			names := keybase1.RefNames(body.Refs)
  3062  			return fmt.Sprintf("git (%s) @%s pushed %d commits to %s", body.RepoName,
  3063  				body.Pusher, total, names)
  3064  		}
  3065  	case MessageSystemType_CHANGEAVATAR:
  3066  		return fmt.Sprintf("@%s changed team avatar", m.Changeavatar().User)
  3067  	case MessageSystemType_CHANGERETENTION:
  3068  		return m.Changeretention().String()
  3069  	case MessageSystemType_BULKADDTOCONV:
  3070  		return m.Bulkaddtoconv().String()
  3071  	case MessageSystemType_SBSRESOLVE:
  3072  		body := m.Sbsresolve()
  3073  		switch body.AssertionService {
  3074  		case "phone":
  3075  			return fmt.Sprintf("@%s verified their phone number %s and joined"+
  3076  				" the conversation", body.Prover, body.AssertionUsername)
  3077  		case "email":
  3078  			return fmt.Sprintf("@%s verified their email address %s and joined"+
  3079  				" the conversation", body.Prover, body.AssertionUsername)
  3080  		}
  3081  		return fmt.Sprintf("@%s proved they are %s on %s and joined"+
  3082  			" the conversation", body.Prover, body.AssertionUsername,
  3083  			body.AssertionService)
  3084  	case MessageSystemType_NEWCHANNEL:
  3085  		body := m.Newchannel()
  3086  		if len(body.ConvIDs) > 1 {
  3087  			return fmt.Sprintf("@%s created #%s and %d other new channels",
  3088  				body.Creator, body.NameAtCreation, len(body.ConvIDs)-1)
  3089  		}
  3090  		return fmt.Sprintf("@%s created a new channel #%s",
  3091  			body.Creator, body.NameAtCreation)
  3092  	default:
  3093  		return ""
  3094  	}
  3095  }
  3096  
  3097  func (m MessageHeadline) String() string {
  3098  	if m.Headline == "" {
  3099  		return "cleared the channel description"
  3100  	}
  3101  	return fmt.Sprintf("set the channel description: %v", m.Headline)
  3102  }
  3103  
  3104  func isZero(v []byte) bool {
  3105  	for _, b := range v {
  3106  		if b != 0 {
  3107  			return false
  3108  		}
  3109  	}
  3110  	return true
  3111  }
  3112  
  3113  func MakeFlipGameID(s string) (FlipGameID, error) { return hex.DecodeString(s) }
  3114  func (g FlipGameID) String() string               { return hex.EncodeToString(g) }
  3115  func (g FlipGameID) FlipGameIDStr() FlipGameIDStr { return FlipGameIDStr(g.String()) }
  3116  func (g FlipGameID) Eq(h FlipGameID) bool         { return hmac.Equal(g[:], h[:]) }
  3117  func (g FlipGameID) IsZero() bool                 { return isZero(g[:]) }
  3118  func (g FlipGameID) Check() bool                  { return g != nil && !g.IsZero() }
  3119  
  3120  func (o *SenderSendOptions) GetJoinMentionsAs() *ConversationMemberStatus {
  3121  	if o == nil {
  3122  		return nil
  3123  	}
  3124  	return o.JoinMentionsAs
  3125  }
  3126  
  3127  func (c Coordinate) IsZero() bool {
  3128  	return c.Lat == 0 && c.Lon == 0
  3129  }
  3130  
  3131  func (c Coordinate) Eq(o Coordinate) bool {
  3132  	return c.Lat == o.Lat && c.Lon == o.Lon
  3133  }
  3134  
  3135  type safeCoordinate struct {
  3136  	Lat      float64 `codec:"lat" json:"lat"`
  3137  	Lon      float64 `codec:"lon" json:"lon"`
  3138  	Accuracy float64 `codec:"accuracy" json:"accuracy"`
  3139  }
  3140  
  3141  func (c Coordinate) MarshalJSON() ([]byte, error) {
  3142  	var safe safeCoordinate
  3143  	safe.Lat = c.Lat
  3144  	safe.Lon = c.Lon
  3145  	safe.Accuracy = c.Accuracy
  3146  	if math.IsNaN(safe.Lat) {
  3147  		safe.Lat = 0
  3148  	}
  3149  	if math.IsNaN(safe.Lon) {
  3150  		safe.Lon = 0
  3151  	}
  3152  	if math.IsNaN(safe.Accuracy) {
  3153  		safe.Accuracy = 0
  3154  	}
  3155  	return json.Marshal(safe)
  3156  }
  3157  
  3158  // Incremented if the client hash algorithm changes. If this value is changed
  3159  // be sure to add a case in the BotInfo.Hash() function.
  3160  const ClientBotInfoHashVers BotInfoHashVers = 2
  3161  
  3162  // Incremented if the server sends down bad data and needs to bust client
  3163  // caches.
  3164  const ServerBotInfoHashVers BotInfoHashVers = 1
  3165  
  3166  func (b BotInfo) Hash() BotInfoHash {
  3167  	hash := sha256Pool.Get().(hash.Hash)
  3168  	defer sha256Pool.Put(hash)
  3169  	hash.Reset()
  3170  
  3171  	// Always hash in the server/client version.
  3172  	hash.Write([]byte(strconv.FormatUint(uint64(b.ServerHashVers), 10)))
  3173  	hash.Write([]byte(strconv.FormatUint(uint64(b.ClientHashVers), 10)))
  3174  
  3175  	// This should cover all cases from 0..DefaultBotInfoHashVers. If
  3176  	// incrementing DefaultBotInfoHashVers be sure to add a case here.
  3177  	switch b.ClientHashVers {
  3178  	case 0, 1:
  3179  		b.hashV1(hash)
  3180  	case 2:
  3181  		b.hashV2(hash)
  3182  	default:
  3183  		// Every valid client version should be specifically handled, unit
  3184  		// tests verify that we have a non-empty hash output.
  3185  		hash.Reset()
  3186  	}
  3187  	return BotInfoHash(hash.Sum(nil))
  3188  }
  3189  
  3190  func (b BotInfo) hashV1(hash hash.Hash) {
  3191  	sort.Slice(b.CommandConvs, func(i, j int) bool {
  3192  		ikey := b.CommandConvs[i].Uid.String() + b.CommandConvs[i].ConvID.String()
  3193  		jkey := b.CommandConvs[j].Uid.String() + b.CommandConvs[j].ConvID.String()
  3194  		return ikey < jkey
  3195  	})
  3196  	for _, cconv := range b.CommandConvs {
  3197  		hash.Write(cconv.ConvID)
  3198  		hash.Write(cconv.Uid)
  3199  		hash.Write([]byte(strconv.FormatUint(uint64(cconv.UntrustedTeamRole), 10)))
  3200  		hash.Write([]byte(strconv.FormatUint(uint64(cconv.Vers), 10)))
  3201  	}
  3202  }
  3203  
  3204  func (b BotInfo) hashV2(hash hash.Hash) {
  3205  	sort.Slice(b.CommandConvs, func(i, j int) bool {
  3206  		ikey := b.CommandConvs[i].Uid.String() + b.CommandConvs[i].ConvID.String()
  3207  		jkey := b.CommandConvs[j].Uid.String() + b.CommandConvs[j].ConvID.String()
  3208  		return ikey < jkey
  3209  	})
  3210  	for _, cconv := range b.CommandConvs {
  3211  		hash.Write(cconv.ConvID)
  3212  		hash.Write(cconv.Uid)
  3213  		hash.Write([]byte(strconv.FormatUint(uint64(cconv.UntrustedTeamRole), 10)))
  3214  		hash.Write([]byte(strconv.FormatUint(uint64(cconv.Vers), 10)))
  3215  		hash.Write([]byte(strconv.FormatUint(uint64(cconv.Typ), 10)))
  3216  	}
  3217  }
  3218  
  3219  func (b BotInfoHash) Eq(h BotInfoHash) bool {
  3220  	return bytes.Equal(b, h)
  3221  }
  3222  
  3223  func (p AdvertiseCommandsParam) ToRemote(cmdConvID ConversationID, tlfID *TLFID, adConvID *ConversationID) (res RemoteBotCommandsAdvertisement, err error) {
  3224  	switch p.Typ {
  3225  	case BotCommandsAdvertisementTyp_PUBLIC:
  3226  		if tlfID != nil {
  3227  			return res, errors.New("TLFID specified for public advertisement")
  3228  		} else if adConvID != nil {
  3229  			return res, errors.New("ConvID specified for public advertisement")
  3230  		}
  3231  		return NewRemoteBotCommandsAdvertisementWithPublic(RemoteBotCommandsAdvertisementPublic{
  3232  			ConvID: cmdConvID,
  3233  		}), nil
  3234  	case BotCommandsAdvertisementTyp_TLFID_CONVS:
  3235  		if tlfID == nil {
  3236  			return res, errors.New("no TLFID specified")
  3237  		} else if adConvID != nil {
  3238  			return res, errors.New("ConvID specified")
  3239  		}
  3240  		return NewRemoteBotCommandsAdvertisementWithTlfidConvs(RemoteBotCommandsAdvertisementTLFID{
  3241  			ConvID: cmdConvID,
  3242  			TlfID:  *tlfID,
  3243  		}), nil
  3244  	case BotCommandsAdvertisementTyp_TLFID_MEMBERS:
  3245  		if tlfID == nil {
  3246  			return res, errors.New("no TLFID specified")
  3247  		} else if adConvID != nil {
  3248  			return res, errors.New("ConvID specified")
  3249  		}
  3250  		return NewRemoteBotCommandsAdvertisementWithTlfidMembers(RemoteBotCommandsAdvertisementTLFID{
  3251  			ConvID: cmdConvID,
  3252  			TlfID:  *tlfID,
  3253  		}), nil
  3254  	case BotCommandsAdvertisementTyp_CONV:
  3255  		if tlfID != nil {
  3256  			return res, errors.New("TLFID specified")
  3257  		} else if adConvID == nil {
  3258  			return res, errors.New("no ConvID specified")
  3259  		}
  3260  		return NewRemoteBotCommandsAdvertisementWithConv(RemoteBotCommandsAdvertisementConv{
  3261  			ConvID:          cmdConvID,
  3262  			AdvertiseConvID: *adConvID,
  3263  		}), nil
  3264  	default:
  3265  		return res, errors.New("unknown bot advertisement typ")
  3266  	}
  3267  }
  3268  
  3269  func (p ClearBotCommandsFilter) ToRemote(tlfID *TLFID, convID *ConversationID) (res RemoteClearBotCommandsFilter, err error) {
  3270  	switch p.Typ {
  3271  	case BotCommandsAdvertisementTyp_PUBLIC:
  3272  		if tlfID != nil {
  3273  			return res, errors.New("TLFID specified for public advertisement")
  3274  		} else if convID != nil {
  3275  			return res, errors.New("ConvID specified for public advertisement")
  3276  		}
  3277  		return NewRemoteClearBotCommandsFilterWithPublic(RemoteClearBotCommandsFilterPublic{}), nil
  3278  	case BotCommandsAdvertisementTyp_TLFID_CONVS:
  3279  		if tlfID == nil {
  3280  			return res, errors.New("no TLFID specified")
  3281  		} else if convID != nil {
  3282  			return res, errors.New("ConvID specified")
  3283  		}
  3284  		return NewRemoteClearBotCommandsFilterWithTlfidConvs(RemoteClearBotCommandsFilterTLFID{
  3285  			TlfID: *tlfID,
  3286  		}), nil
  3287  	case BotCommandsAdvertisementTyp_TLFID_MEMBERS:
  3288  		if tlfID == nil {
  3289  			return res, errors.New("no TLFID specified")
  3290  		} else if convID != nil {
  3291  			return res, errors.New("ConvID specified")
  3292  		}
  3293  		return NewRemoteClearBotCommandsFilterWithTlfidMembers(RemoteClearBotCommandsFilterTLFID{
  3294  			TlfID: *tlfID,
  3295  		}), nil
  3296  	case BotCommandsAdvertisementTyp_CONV:
  3297  		if tlfID != nil {
  3298  			return res, errors.New("TLFID specified")
  3299  		} else if convID == nil {
  3300  			return res, errors.New("no ConvID specified")
  3301  		}
  3302  		return NewRemoteClearBotCommandsFilterWithConv(RemoteClearBotCommandsFilterConv{
  3303  			ConvID: *convID,
  3304  		}), nil
  3305  	default:
  3306  		return res, errors.New("unknown bot advertisement typ")
  3307  	}
  3308  }
  3309  
  3310  func GetAdvertTyp(typ string) (BotCommandsAdvertisementTyp, error) {
  3311  	switch typ {
  3312  	case "public":
  3313  		return BotCommandsAdvertisementTyp_PUBLIC, nil
  3314  	case "teamconvs":
  3315  		return BotCommandsAdvertisementTyp_TLFID_CONVS, nil
  3316  	case "teammembers":
  3317  		return BotCommandsAdvertisementTyp_TLFID_MEMBERS, nil
  3318  	case "conv":
  3319  		return BotCommandsAdvertisementTyp_CONV, nil
  3320  	default:
  3321  		return BotCommandsAdvertisementTyp_PUBLIC, fmt.Errorf("unknown advertisement type %q, must be one of 'public', 'teamconvs', 'teammembers', or 'conv' see `keybase chat api --help` for more info.", typ)
  3322  	}
  3323  }
  3324  
  3325  func (c UserBotCommandInput) ToOutput(username string) UserBotCommandOutput {
  3326  	return UserBotCommandOutput{
  3327  		Name:                c.Name,
  3328  		Description:         c.Description,
  3329  		Usage:               c.Usage,
  3330  		ExtendedDescription: c.ExtendedDescription,
  3331  		Username:            username,
  3332  	}
  3333  }
  3334  
  3335  func (r UIInboxReselectInfo) String() string {
  3336  	newConvStr := "<none>"
  3337  	if r.NewConvID != nil {
  3338  		newConvStr = string(*r.NewConvID)
  3339  	}
  3340  	return fmt.Sprintf("[oldconv: %s newconv: %s]", r.OldConvID, newConvStr)
  3341  }
  3342  
  3343  func (e OutboxErrorType) IsBadgableError() bool {
  3344  	switch e {
  3345  	case OutboxErrorType_MISC,
  3346  		OutboxErrorType_OFFLINE,
  3347  		OutboxErrorType_TOOLONG,
  3348  		OutboxErrorType_EXPIRED,
  3349  		OutboxErrorType_TOOMANYATTEMPTS,
  3350  		OutboxErrorType_UPLOADFAILED:
  3351  		return true
  3352  	default:
  3353  		return false
  3354  	}
  3355  }
  3356  
  3357  func (c UserBotCommandOutput) Matches(text string) bool {
  3358  	return strings.HasPrefix(text, fmt.Sprintf("!%s", c.Name))
  3359  }
  3360  
  3361  func (m AssetMetadata) IsType(typ AssetMetadataType) bool {
  3362  	mtyp, err := m.AssetType()
  3363  	if err != nil {
  3364  		return false
  3365  	}
  3366  	return mtyp == typ
  3367  }
  3368  
  3369  type safeAssetMetadataImage struct {
  3370  	Width     int       `codec:"width" json:"width"`
  3371  	Height    int       `codec:"height" json:"height"`
  3372  	AudioAmps []float64 `codec:"audioAmps" json:"audioAmps"`
  3373  }
  3374  
  3375  func (m AssetMetadataImage) MarshalJSON() ([]byte, error) {
  3376  	var safe safeAssetMetadataImage
  3377  	safe.AudioAmps = make([]float64, 0, len(m.AudioAmps))
  3378  	for _, amp := range m.AudioAmps {
  3379  		if math.IsNaN(amp) {
  3380  			safe.AudioAmps = append(safe.AudioAmps, 0)
  3381  		} else {
  3382  			safe.AudioAmps = append(safe.AudioAmps, amp)
  3383  		}
  3384  	}
  3385  	safe.Width = m.Width
  3386  	safe.Height = m.Height
  3387  	return json.Marshal(safe)
  3388  }
  3389  
  3390  func (s SnippetDecoration) ToEmoji() string {
  3391  	switch s {
  3392  	case SnippetDecoration_PENDING_MESSAGE:
  3393  		return ":outbox_tray:"
  3394  	case SnippetDecoration_FAILED_PENDING_MESSAGE:
  3395  		return ":warning:"
  3396  	case SnippetDecoration_EXPLODING_MESSAGE:
  3397  		return ":bomb:"
  3398  	case SnippetDecoration_EXPLODED_MESSAGE:
  3399  		return ":boom:"
  3400  	case SnippetDecoration_AUDIO_ATTACHMENT:
  3401  		return ":loud_sound:"
  3402  	case SnippetDecoration_VIDEO_ATTACHMENT:
  3403  		return ":film_frames:"
  3404  	case SnippetDecoration_PHOTO_ATTACHMENT:
  3405  		return ":camera:"
  3406  	case SnippetDecoration_FILE_ATTACHMENT:
  3407  		return ":file_folder:"
  3408  	case SnippetDecoration_STELLAR_RECEIVED:
  3409  		return ":bank:"
  3410  	case SnippetDecoration_STELLAR_SENT:
  3411  		return ":money_with_wings:"
  3412  	case SnippetDecoration_PINNED_MESSAGE:
  3413  		return ":pushpin:"
  3414  	default:
  3415  		return ""
  3416  	}
  3417  }
  3418  
  3419  func (r EmojiRemoteSource) IsMessage() bool {
  3420  	typ, err := r.Typ()
  3421  	if err != nil {
  3422  		return false
  3423  	}
  3424  	return typ == EmojiRemoteSourceTyp_MESSAGE
  3425  }
  3426  
  3427  func (r EmojiRemoteSource) IsStockAlias() bool {
  3428  	typ, err := r.Typ()
  3429  	if err != nil {
  3430  		return false
  3431  	}
  3432  	return typ == EmojiRemoteSourceTyp_STOCKALIAS
  3433  }
  3434  
  3435  func (r EmojiRemoteSource) IsAlias() bool {
  3436  	return r.IsStockAlias() || (r.IsMessage() && r.Message().IsAlias)
  3437  }
  3438  
  3439  func (r EmojiLoadSource) IsHTTPSrv() bool {
  3440  	typ, err := r.Typ()
  3441  	if err != nil {
  3442  		return false
  3443  	}
  3444  	return typ == EmojiLoadSourceTyp_HTTPSRV
  3445  }
  3446  
  3447  func TeamToChatMemberDetails(teamMembers []keybase1.TeamMemberDetails) (chatMembers []ChatMemberDetails) {
  3448  	chatMembers = make([]ChatMemberDetails, len(teamMembers))
  3449  	for i, teamMember := range teamMembers {
  3450  		chatMembers[i] = ChatMemberDetails{
  3451  			Uid:      teamMember.Uv.Uid,
  3452  			Username: teamMember.Username,
  3453  			FullName: teamMember.FullName,
  3454  		}
  3455  	}
  3456  	return chatMembers
  3457  }
  3458  
  3459  func TeamToChatMembersDetails(details keybase1.TeamMembersDetails) ChatMembersDetails {
  3460  	return ChatMembersDetails{
  3461  		Owners:         TeamToChatMemberDetails(details.Owners),
  3462  		Admins:         TeamToChatMemberDetails(details.Admins),
  3463  		Writers:        TeamToChatMemberDetails(details.Writers),
  3464  		Readers:        TeamToChatMemberDetails(details.Readers),
  3465  		Bots:           TeamToChatMemberDetails(details.Bots),
  3466  		RestrictedBots: TeamToChatMemberDetails(details.RestrictedBots),
  3467  	}
  3468  }