1package chat1 2 3import ( 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 28const ShowExplosionLifetime = time.Hour * 24 * 7 29 30// If a conversation is larger, only admins can @channel. 31const MaxChanMentionConvSize = 100 32 33func (i FlipGameIDStr) String() string { 34 return string(i) 35} 36 37func (i TLFIDStr) String() string { 38 return string(i) 39} 40 41func (i ConvIDStr) String() string { 42 return string(i) 43} 44 45type ByUID []gregor1.UID 46type ConvIDShort = []byte 47 48func (b ByUID) Len() int { return len(b) } 49func (b ByUID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 50func (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 55func (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. 61func (id TLFID) EqString(other fmt.Stringer) bool { 62 return hex.EncodeToString(id) == other.String() 63} 64 65func (id TLFID) String() string { 66 return hex.EncodeToString(id) 67} 68 69func (id TLFID) Bytes() []byte { 70 return []byte(id) 71} 72 73func (id TLFID) TLFIDStr() TLFIDStr { 74 return TLFIDStr(id.String()) 75} 76 77func (id TLFID) IsNil() bool { 78 return len(id) == 0 79} 80 81func (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 96func MakeConvID(val string) (ConversationID, error) { 97 return hex.DecodeString(val) 98} 99 100func (cid ConversationID) String() string { 101 return hex.EncodeToString(cid) 102} 103 104func (cid ConversationID) Bytes() []byte { 105 return []byte(cid) 106} 107 108func (cid ConversationID) ConvIDStr() ConvIDStr { 109 return ConvIDStr(cid.String()) 110} 111 112func (cid ConversationID) IsNil() bool { 113 return len(cid) < DbShortFormLen 114} 115 116func (cid ConversationID) Eq(c ConversationID) bool { 117 return bytes.Equal(cid, c) 118} 119 120func (cid ConversationID) Less(c ConversationID) bool { 121 return bytes.Compare(cid, c) < 0 122} 123 124const DbShortFormLen = 10 125 126// DbShortForm should only be used when interacting with the database, and should 127// never leave Gregor 128func (cid ConversationID) DbShortForm() ConvIDShort { 129 end := DbShortFormLen 130 if end > len(cid) { 131 end = len(cid) 132 } 133 return cid[:end] 134} 135 136func (cid ConversationID) DbShortFormString() string { 137 return DbShortFormToString(cid.DbShortForm()) 138} 139 140func DbShortFormToString(cid ConvIDShort) string { 141 return hex.EncodeToString(cid) 142} 143 144func DbShortFormFromString(cid string) (ConvIDShort, error) { 145 return hex.DecodeString(cid) 146} 147 148func MakeTLFID(val string) (TLFID, error) { 149 return hex.DecodeString(val) 150} 151 152func MakeTopicID(val string) (TopicID, error) { 153 return hex.DecodeString(val) 154} 155 156func MakeTopicType(val int64) TopicType { 157 return TopicType(val) 158} 159 160func (mid MessageID) String() string { 161 return strconv.FormatUint(uint64(mid), 10) 162} 163 164func (mid MessageID) Min(mid2 MessageID) MessageID { 165 if mid < mid2 { 166 return mid 167 } 168 return mid2 169} 170 171func (mid MessageID) IsNil() bool { 172 return uint(mid) == 0 173} 174 175func (mid MessageID) Advance(num uint) MessageID { 176 return MessageID(uint(mid) + num) 177} 178 179func (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. 188var 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. 203var nonDeletableMessageTypesByDeleteHistory = []MessageType{ 204 MessageType_NONE, 205 MessageType_DELETE, 206 MessageType_TLFNAME, 207 MessageType_DELETEHISTORY, 208} 209 210func DeletableMessageTypesByDelete() []MessageType { 211 return deletableMessageTypesByDelete 212} 213 214func 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 233var 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. 248func VisibleChatMessageTypes() []MessageType { 249 return visibleMessageTypes 250} 251 252var 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. 265func 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. 271func NonEmptyConvMessageTypes() []MessageType { 272 return badgeableMessageTypes 273} 274 275var 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. 288func SnippetChatMessageTypes() []MessageType { 289 return snippetMessageTypes 290} 291 292var editableMessageTypesByEdit = []MessageType{ 293 MessageType_TEXT, 294 MessageType_ATTACHMENT, 295} 296 297func EditableMessageTypesByEdit() []MessageType { 298 return editableMessageTypesByEdit 299} 300 301func 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 313func 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 324func IsEphemeralType(typ MessageType) bool { 325 return IsEphemeralNonSupersederType(typ) || IsEphemeralSupersederType(typ) 326} 327 328func 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 344func IsDeletableByDelete(typ MessageType) bool { 345 for _, typ2 := range deletableMessageTypesByDelete { 346 if typ == typ2 { 347 return true 348 } 349 } 350 return false 351} 352 353func 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. 364func (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. 377func (t TopicType) EphemeralRequired() bool { 378 switch t { 379 case TopicType_DEV: 380 return false 381 default: 382 return t.EphemeralAllowed() 383 } 384} 385 386func (t TopicType) String() string { 387 s, ok := TopicTypeRevMap[t] 388 if ok { 389 return s 390 } 391 return "UNKNOWN" 392} 393 394func (t TopicID) String() string { 395 return hex.EncodeToString(t) 396} 397 398func (t TopicID) Eq(r TopicID) bool { 399 return bytes.Equal([]byte(t), []byte(r)) 400} 401 402func (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 408func (hash Hash) String() string { 409 return hex.EncodeToString(hash) 410} 411 412func (hash Hash) Eq(other Hash) bool { 413 return bytes.Equal(hash, other) 414} 415 416func (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 432func (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 450func (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 470func (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 482func (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 494func (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 515func (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 533func (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 553func (m MessageUnboxed) IsValid() bool { 554 if state, err := m.State(); err == nil { 555 return state == MessageUnboxedState_VALID 556 } 557 return false 558} 559 560func (m MessageUnboxed) IsError() bool { 561 if state, err := m.State(); err == nil { 562 return state == MessageUnboxedState_ERROR 563 } 564 return false 565} 566 567func (m MessageUnboxed) IsOutbox() bool { 568 if state, err := m.State(); err == nil { 569 return state == MessageUnboxedState_OUTBOX 570 } 571 return false 572} 573 574func (m MessageUnboxed) IsPlaceholder() bool { 575 if state, err := m.State(); err == nil { 576 return state == MessageUnboxedState_PLACEHOLDER 577 } 578 return false 579} 580 581func (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) 592func (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 614func (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 634func (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 644func (m MessageUnboxed) HasReactions() bool { 645 if !m.IsValid() { 646 return false 647 } 648 return len(m.Valid().Reactions.Reactions) > 0 649} 650 651func (m MessageUnboxed) HasUnfurls() bool { 652 if !m.IsValid() { 653 return false 654 } 655 return len(m.Valid().Unfurls) > 0 656} 657 658func (m MessageUnboxed) SearchableText() string { 659 if !m.IsValidFull() { 660 return "" 661 } 662 return m.Valid().MessageBody.SearchableText() 663} 664 665func (m MessageUnboxed) SenderUsername() string { 666 if !m.IsValid() { 667 return "" 668 } 669 return m.Valid().SenderUsername 670} 671 672func (m MessageUnboxed) Ctime() gregor1.Time { 673 if !m.IsValid() { 674 return 0 675 } 676 return m.Valid().ServerHeader.Ctime 677} 678 679func (m MessageUnboxed) AtMentionUsernames() []string { 680 if !m.IsValid() { 681 return nil 682 } 683 return m.Valid().AtMentionUsernames 684} 685 686func (m MessageUnboxed) ChannelMention() ChannelMention { 687 if !m.IsValid() { 688 return ChannelMention_NONE 689 } 690 return m.Valid().ChannelMention 691} 692 693func (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 701func (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 ostateStr := "CORRUPT" 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 754func MessageUnboxedDebugStrings(ms []MessageUnboxed) (res []string) { 755 for _, m := range ms { 756 res = append(res, m.DebugString()) 757 } 758 return res 759} 760 761func MessageUnboxedDebugList(ms []MessageUnboxed) string { 762 return fmt.Sprintf("{ %v %v }", len(ms), strings.Join(MessageUnboxedDebugStrings(ms), ",")) 763} 764 765func MessageUnboxedDebugLines(ms []MessageUnboxed) string { 766 return strings.Join(MessageUnboxedDebugStrings(ms), "\n") 767} 768 769const ( 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. 778var MaxMessageBoxedVersion MessageBoxedVersion = MessageBoxedVersion_V4 779var MaxHeaderVersion HeaderPlaintextVersion = HeaderPlaintextVersion_V1 780var 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. 784func (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 825func (m MessageUnboxedError) IsEphemeralError() bool { 826 return m.IsEphemeral && (m.ErrType == MessageUnboxedErrorType_EPHEMERAL || m.ErrType == MessageUnboxedErrorType_PAIRWISE_MISSING) 827} 828 829func (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 840func (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 849func (m MessageUnboxedError) SenderEq(o MessageUnboxedError) bool { 850 return m.SenderUsername == o.SenderUsername 851} 852 853func (m OutboxRecord) SenderEq(o OutboxRecord) bool { 854 return m.Msg.ClientHeader.Sender.Eq(o.Msg.ClientHeader.Sender) 855} 856 857func (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 874func (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 887func (m MessagePlaintext) MessageType() MessageType { 888 typ, err := m.MessageBody.MessageType() 889 if err != nil { 890 return MessageType_NONE 891 } 892 return typ 893} 894 895func (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 905func (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 915func (m MessagePlaintext) SearchableText() string { 916 return m.MessageBody.SearchableText() 917} 918 919func (m MessagePlaintext) IsEphemeral() bool { 920 return m.EphemeralMetadata() != nil 921} 922 923func (m MessagePlaintext) EphemeralMetadata() *MsgEphemeralMetadata { 924 return m.ClientHeader.EphemeralMetadata 925} 926 927func (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 934func (m MessageUnboxedValid) SenderEq(o MessageUnboxedValid) bool { 935 return m.ClientHeader.Sender.Eq(o.ClientHeader.Sender) 936} 937 938func (m MessageUnboxedValid) HasPairwiseMacs() bool { 939 return m.ClientHeader.HasPairwiseMacs 940} 941 942func (m MessageUnboxedValid) IsEphemeral() bool { 943 return m.EphemeralMetadata() != nil 944} 945 946func (m MessageUnboxedValid) EphemeralMetadata() *MsgEphemeralMetadata { 947 return m.ClientHeader.EphemeralMetadata 948} 949 950func (m MessageUnboxedValid) ExplodedBy() *string { 951 if !m.IsEphemeral() { 952 return nil 953 } 954 return m.EphemeralMetadata().ExplodedBy 955} 956 957func 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 970func (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 982func (m MessageUnboxedValid) RemainingEphemeralLifetime(now time.Time) time.Duration { 983 remainingLifetime := m.Etime().Time().Sub(now).Round(time.Second) 984 return remainingLifetime 985} 986 987func (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 999func (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 1008func (b MessageBody) IsNil() bool { 1009 return b == MessageBody{} 1010} 1011 1012func (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 1020func (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 1038func (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 1063func (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 1084func (m UIMessage) IsValid() bool { 1085 if state, err := m.State(); err == nil { 1086 return state == MessageUnboxedState_VALID 1087 } 1088 return false 1089} 1090 1091func (m UIMessage) IsError() bool { 1092 if state, err := m.State(); err == nil { 1093 return state == MessageUnboxedState_ERROR 1094 } 1095 return false 1096} 1097 1098func (m UIMessage) IsOutbox() bool { 1099 if state, err := m.State(); err == nil { 1100 return state == MessageUnboxedState_OUTBOX 1101 } 1102 return false 1103} 1104 1105func (m UIMessage) IsPlaceholder() bool { 1106 if state, err := m.State(); err == nil { 1107 return state == MessageUnboxedState_PLACEHOLDER 1108 } 1109 return false 1110} 1111 1112func (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 1127func (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 1150func (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 1172func (m UIMessage) SearchableText() string { 1173 if !m.IsValid() { 1174 return "" 1175 } 1176 return m.Valid().MessageBody.SearchableText() 1177} 1178 1179func (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 1194func (m MessageBoxed) GetMessageID() MessageID { 1195 return m.ServerHeader.MessageID 1196} 1197 1198func (m MessageBoxed) Ctime() gregor1.Time { 1199 return m.ServerHeader.Ctime 1200} 1201 1202func (m MessageBoxed) GetMessageType() MessageType { 1203 return m.ClientHeader.MessageType 1204} 1205 1206func (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 1219func (m MessageBoxed) OutboxInfo() *OutboxInfo { 1220 return m.ClientHeader.OutboxInfo 1221} 1222 1223func (m MessageBoxed) KBFSEncrypted() bool { 1224 return m.ClientHeader.KbfsCryptKeysUsed == nil || *m.ClientHeader.KbfsCryptKeysUsed 1225} 1226 1227func (m MessageBoxed) EphemeralMetadata() *MsgEphemeralMetadata { 1228 return m.ClientHeader.EphemeralMetadata 1229} 1230 1231func (m MessageBoxed) IsEphemeral() bool { 1232 return m.EphemeralMetadata() != nil 1233} 1234 1235func (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 1250func (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 1258func (m MessageBoxed) ExplodedBy() *string { 1259 if !m.IsEphemeral() { 1260 return nil 1261 } 1262 return m.EphemeralMetadata().ExplodedBy 1263} 1264 1265var 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 1274var 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 1283var sha256Pool = sync.Pool{ 1284 New: func() interface{} { 1285 return sha256.New() 1286 }, 1287} 1288 1289func (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 1301func (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 1307func (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 1315func MakeOutboxID(s string) (OutboxID, error) { 1316 b, err := hex.DecodeString(s) 1317 return OutboxID(b), err 1318} 1319 1320func (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 1327func (o OutboxID) String() string { 1328 return hex.EncodeToString(o) 1329} 1330 1331func (o OutboxID) Bytes() []byte { 1332 return []byte(o) 1333} 1334 1335func (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 1342func (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 1350func (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 1358func (o OutboxRecord) IsAttachment() bool { 1359 return o.Msg.ClientHeader.MessageType == MessageType_ATTACHMENT 1360} 1361 1362func (o OutboxRecord) IsUnfurl() bool { 1363 return o.Msg.ClientHeader.MessageType == MessageType_UNFURL 1364} 1365 1366func (o OutboxRecord) IsChatFlip() bool { 1367 return o.Msg.ClientHeader.MessageType == MessageType_FLIP && 1368 o.Msg.ClientHeader.Conv.TopicType == TopicType_CHAT 1369} 1370 1371func (o OutboxRecord) MessageType() MessageType { 1372 return o.Msg.ClientHeader.MessageType 1373} 1374 1375func (o OutboxRecord) IsBadgable() bool { 1376 return o.Msg.ClientHeader.Conv.TopicType == TopicType_CHAT && 1377 o.Msg.IsBadgableType() 1378} 1379 1380func (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. 1388func (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. 1400func (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. 1410func (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. 1417func (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. 1426func (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. 1432func (m MessageSummary) TLFNameExpanded(finalizeInfo *ConversationFinalizeInfo) string { 1433 return ExpandTLFName(m.TlfName, finalizeInfo) 1434} 1435 1436func (h MessageClientHeaderVerified) TLFNameExpanded(finalizeInfo *ConversationFinalizeInfo) string { 1437 return ExpandTLFName(h.TlfName, finalizeInfo) 1438} 1439 1440func (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. 1459func 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). 1475func (f *ConversationFinalizeInfo) BeforeSummary() string { 1476 return fmt.Sprintf("(before %s account reset %s)", f.ResetUser, f.ResetDate) 1477} 1478 1479func (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 1485func (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 1496func (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 1505func (p *Pagination) FirstPage() bool { 1506 return p == nil || p.ForceFirstPage || (len(p.Next) == 0 && len(p.Previous) == 0) 1507} 1508 1509func (c ConversationLocal) GetMtime() gregor1.Time { 1510 return c.ReaderInfo.Mtime 1511} 1512 1513func (c ConversationLocal) GetConvID() ConversationID { 1514 return c.Info.Id 1515} 1516 1517func (c ConversationLocal) GetTopicType() TopicType { 1518 return c.Info.Triple.TopicType 1519} 1520 1521func (c ConversationLocal) GetMembersType() ConversationMembersType { 1522 return c.Info.MembersType 1523} 1524 1525func (c ConversationLocal) GetTeamType() TeamType { 1526 return c.Info.TeamType 1527} 1528 1529func (c ConversationLocal) GetFinalizeInfo() *ConversationFinalizeInfo { 1530 return c.Info.FinalizeInfo 1531} 1532 1533func (c ConversationLocal) GetTopicName() string { 1534 return c.Info.TopicName 1535} 1536 1537func (c ConversationLocal) GetExpunge() *Expunge { 1538 return &c.Expunge 1539} 1540 1541func (c ConversationLocal) IsPublic() bool { 1542 return c.Info.Visibility == keybase1.TLFVisibility_PUBLIC 1543} 1544 1545func (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 1554func (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 1568func 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 1583func (c ConversationLocal) MaxVisibleMsgID() MessageID { 1584 return maxVisibleMsgIDFromSummaries(c.MaxMessages) 1585} 1586 1587func (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 1596func (c ConversationLocal) AllNames() (res []string) { 1597 for _, p := range c.Info.Participants { 1598 res = append(res, p.Username) 1599 } 1600 return res 1601} 1602 1603func (c ConversationLocal) FullNamesForSearch() (res []*string) { 1604 for _, p := range c.Info.Participants { 1605 res = append(res, p.Fullname) 1606 } 1607 return res 1608} 1609 1610func (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 1618func (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 1626func (c Conversation) GetMtime() gregor1.Time { 1627 return c.ReaderInfo.Mtime 1628} 1629 1630func (c Conversation) GetConvID() ConversationID { 1631 return c.Metadata.ConversationID 1632} 1633 1634func (c Conversation) GetTopicType() TopicType { 1635 return c.Metadata.IdTriple.TopicType 1636} 1637 1638func (c Conversation) GetMembersType() ConversationMembersType { 1639 return c.Metadata.MembersType 1640} 1641 1642func (c Conversation) GetTeamType() TeamType { 1643 return c.Metadata.TeamType 1644} 1645 1646func (c Conversation) GetFinalizeInfo() *ConversationFinalizeInfo { 1647 return c.Metadata.FinalizeInfo 1648} 1649 1650func (c Conversation) GetExpunge() *Expunge { 1651 return &c.Expunge 1652} 1653 1654func (c Conversation) IsPublic() bool { 1655 return c.Metadata.Visibility == keybase1.TLFVisibility_PUBLIC 1656} 1657 1658var errMaxMessageNotFound = errors.New("max message not found") 1659 1660func (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 1669func (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 1678func (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 1692func (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 1702func (c Conversation) IsSelfFinalized(username string) bool { 1703 return c.GetMembersType() == ConversationMembersType_KBFS && c.GetFinalizeInfo().IsResetForUser(username) 1704} 1705 1706func (c Conversation) MaxVisibleMsgID() MessageID { 1707 return maxVisibleMsgIDFromSummaries(c.MaxMsgSummaries) 1708} 1709 1710func (c Conversation) IsUnread() bool { 1711 return c.IsUnreadFromMsgID(c.ReaderInfo.ReadMsgid) 1712} 1713 1714func (c Conversation) IsUnreadFromMsgID(readMsgID MessageID) bool { 1715 maxMsgID := c.MaxVisibleMsgID() 1716 return maxMsgID > 0 && maxMsgID > readMsgID 1717} 1718 1719func (c Conversation) HasMemberStatus(status ConversationMemberStatus) bool { 1720 if c.ReaderInfo != nil { 1721 return c.ReaderInfo.Status == status 1722 } 1723 return false 1724} 1725 1726func (m MessageSummary) GetMessageID() MessageID { 1727 return m.MsgID 1728} 1729 1730func (m MessageSummary) GetMessageType() MessageType { 1731 return m.MessageType 1732} 1733 1734/* 1735func 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 1770func (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 1777func (d *SealedData) AsEncrypted() EncryptedData { 1778 return EncryptedData{ 1779 V: d.V, 1780 E: d.E, 1781 N: d.N, 1782 } 1783} 1784 1785func (d *SealedData) AsSignEncrypted() SignEncryptedData { 1786 return SignEncryptedData{ 1787 V: d.V, 1788 E: d.E, 1789 N: d.N, 1790 } 1791} 1792 1793func (d *EncryptedData) AsSealed() SealedData { 1794 return SealedData{ 1795 V: d.V, 1796 E: d.E, 1797 N: d.N, 1798 } 1799} 1800 1801func (d *SignEncryptedData) AsSealed() SealedData { 1802 return SealedData{ 1803 V: d.V, 1804 E: d.E, 1805 N: d.N, 1806 } 1807} 1808 1809func 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 1825type OfflinableResult interface { 1826 SetOffline() 1827} 1828 1829func (r *NonblockFetchRes) SetOffline() { 1830 r.Offline = true 1831} 1832 1833func (r *UnreadlineRes) SetOffline() { 1834 r.Offline = true 1835} 1836 1837func (r *MarkAsReadLocalRes) SetOffline() { 1838 r.Offline = true 1839} 1840 1841func (r *MarkTLFAsReadLocalRes) SetOffline() { 1842 r.Offline = true 1843} 1844 1845func (r *GetInboxAndUnboxLocalRes) SetOffline() { 1846 r.Offline = true 1847} 1848 1849func (r *GetInboxAndUnboxUILocalRes) SetOffline() { 1850 r.Offline = true 1851} 1852 1853func (r *GetThreadLocalRes) SetOffline() { 1854 r.Offline = true 1855} 1856 1857func (r *GetInboxSummaryForCLILocalRes) SetOffline() { 1858 r.Offline = true 1859} 1860 1861func (r *GetConversationForCLILocalRes) SetOffline() { 1862 r.Offline = true 1863} 1864 1865func (r *GetMessagesLocalRes) SetOffline() { 1866 r.Offline = true 1867} 1868 1869func (r *GetNextAttachmentMessageLocalRes) SetOffline() { 1870 r.Offline = true 1871} 1872 1873func (r *FindConversationsLocalRes) SetOffline() { 1874 r.Offline = true 1875} 1876 1877func (r *JoinLeaveConversationLocalRes) SetOffline() { 1878 r.Offline = true 1879} 1880 1881func (r *PreviewConversationLocalRes) SetOffline() { 1882 r.Offline = true 1883} 1884 1885func (r *GetTLFConversationsLocalRes) SetOffline() { 1886 r.Offline = true 1887} 1888 1889func (r *GetChannelMembershipsLocalRes) SetOffline() { 1890 r.Offline = true 1891} 1892 1893func (r *GetMutualTeamsLocalRes) SetOffline() { 1894 r.Offline = true 1895} 1896 1897func (r *SetAppNotificationSettingsLocalRes) SetOffline() { 1898 r.Offline = true 1899} 1900 1901func (r *DeleteConversationLocalRes) SetOffline() { 1902 r.Offline = true 1903} 1904 1905func (r *SearchRegexpRes) SetOffline() { 1906 r.Offline = true 1907} 1908 1909func (r *SearchInboxRes) SetOffline() { 1910 r.Offline = true 1911} 1912 1913func (t TyperInfo) String() string { 1914 return fmt.Sprintf("typer(u:%s d:%s)", t.Username, t.DeviceID) 1915} 1916 1917func (o TLFConvOrdinal) Int() int { 1918 return int(o) 1919} 1920 1921func (o TLFConvOrdinal) IsFirst() bool { 1922 return o.Int() == 1 1923} 1924 1925func 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 1936func (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 1942func (s TopicNameState) Bytes() []byte { 1943 return []byte(s) 1944} 1945 1946func (s TopicNameState) Eq(o TopicNameState) bool { 1947 return bytes.Equal(s.Bytes(), o.Bytes()) 1948} 1949 1950func (i InboxUIItem) GetConvID() ConversationID { 1951 bConvID, _ := hex.DecodeString(i.ConvID.String()) 1952 return ConversationID(bConvID) 1953} 1954 1955type ByConversationMemberStatus []ConversationMemberStatus 1956 1957func (m ByConversationMemberStatus) Len() int { return len(m) } 1958func (m ByConversationMemberStatus) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 1959func (m ByConversationMemberStatus) Less(i, j int) bool { return m[i] > m[j] } 1960 1961func AllConversationMemberStatuses() (res []ConversationMemberStatus) { 1962 for status := range ConversationMemberStatusRevMap { 1963 res = append(res, status) 1964 } 1965 sort.Sort(ByConversationMemberStatus(res)) 1966 return res 1967} 1968 1969type ByConversationExistence []ConversationExistence 1970 1971func (m ByConversationExistence) Len() int { return len(m) } 1972func (m ByConversationExistence) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 1973func (m ByConversationExistence) Less(i, j int) bool { return m[i] > m[j] } 1974 1975func AllConversationExistences() (res []ConversationExistence) { 1976 for existence := range ConversationExistenceRevMap { 1977 res = append(res, existence) 1978 } 1979 sort.Sort(ByConversationExistence(res)) 1980 return res 1981} 1982 1983func (v InboxVers) ToConvVers() ConversationVers { 1984 return ConversationVers(v) 1985} 1986 1987func (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 1996func (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 2004func (c ConversationMemberStatus) ToGregorDBStringAssert() string { 2005 s, err := c.ToGregorDBString() 2006 if err != nil { 2007 panic(err) 2008 } 2009 return s 2010} 2011 2012func 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 = unit + "s" 2032 } 2033 return fmt.Sprintf("%.0f %s", value, unit) 2034} 2035 2036func (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 2065func (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 2091func (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 2106func TeamIDToTLFID(teamID keybase1.TeamID) (TLFID, error) { 2107 return MakeTLFID(teamID.String()) 2108} 2109 2110func (r *NonblockFetchRes) GetRateLimit() []RateLimit { 2111 return r.RateLimits 2112} 2113 2114func (r *NonblockFetchRes) SetRateLimits(rl []RateLimit) { 2115 r.RateLimits = rl 2116} 2117 2118func (r *UnreadlineRes) GetRateLimit() []RateLimit { 2119 return r.RateLimits 2120} 2121 2122func (r *UnreadlineRes) SetRateLimits(rl []RateLimit) { 2123 r.RateLimits = rl 2124} 2125 2126func (r *MarkAsReadLocalRes) GetRateLimit() []RateLimit { 2127 return r.RateLimits 2128} 2129 2130func (r *MarkAsReadLocalRes) SetRateLimits(rl []RateLimit) { 2131 r.RateLimits = rl 2132} 2133 2134func (r *MarkTLFAsReadLocalRes) GetRateLimit() (res []RateLimit) { 2135 return r.RateLimits 2136} 2137 2138func (r *MarkTLFAsReadLocalRes) SetRateLimits(rl []RateLimit) { 2139 r.RateLimits = rl 2140} 2141 2142func (r *GetInboxAndUnboxLocalRes) GetRateLimit() []RateLimit { 2143 return r.RateLimits 2144} 2145 2146func (r *GetInboxAndUnboxLocalRes) SetRateLimits(rl []RateLimit) { 2147 r.RateLimits = rl 2148} 2149 2150func (r *GetAllResetConvMembersRes) GetRateLimit() []RateLimit { 2151 return r.RateLimits 2152} 2153 2154func (r *GetAllResetConvMembersRes) SetRateLimits(rl []RateLimit) { 2155 r.RateLimits = rl 2156} 2157 2158func (r *LoadFlipRes) GetRateLimit() []RateLimit { 2159 return r.RateLimits 2160} 2161 2162func (r *LoadFlipRes) SetRateLimits(rl []RateLimit) { 2163 r.RateLimits = rl 2164} 2165 2166func (r *GetInboxAndUnboxUILocalRes) GetRateLimit() []RateLimit { 2167 return r.RateLimits 2168} 2169 2170func (r *GetInboxAndUnboxUILocalRes) SetRateLimits(rl []RateLimit) { 2171 r.RateLimits = rl 2172} 2173 2174func (r *GetThreadLocalRes) GetRateLimit() []RateLimit { 2175 return r.RateLimits 2176} 2177 2178func (r *GetThreadLocalRes) SetRateLimits(rl []RateLimit) { 2179 r.RateLimits = rl 2180} 2181 2182func (r *NewConversationLocalRes) GetRateLimit() []RateLimit { 2183 return r.RateLimits 2184} 2185 2186func (r *NewConversationLocalRes) SetRateLimits(rl []RateLimit) { 2187 r.RateLimits = rl 2188} 2189 2190func (r *GetInboxSummaryForCLILocalRes) GetRateLimit() []RateLimit { 2191 return r.RateLimits 2192} 2193 2194func (r *GetInboxSummaryForCLILocalRes) SetRateLimits(rl []RateLimit) { 2195 r.RateLimits = rl 2196} 2197 2198func (r *GetMessagesLocalRes) GetRateLimit() []RateLimit { 2199 return r.RateLimits 2200} 2201 2202func (r *GetMessagesLocalRes) SetRateLimits(rl []RateLimit) { 2203 r.RateLimits = rl 2204} 2205 2206func (r *GetNextAttachmentMessageLocalRes) GetRateLimit() []RateLimit { 2207 return r.RateLimits 2208} 2209 2210func (r *GetNextAttachmentMessageLocalRes) SetRateLimits(rl []RateLimit) { 2211 r.RateLimits = rl 2212} 2213 2214func (r *SetConversationStatusLocalRes) GetRateLimit() []RateLimit { 2215 return r.RateLimits 2216} 2217 2218func (r *SetConversationStatusLocalRes) SetRateLimits(rl []RateLimit) { 2219 r.RateLimits = rl 2220} 2221 2222func (r *PostLocalRes) GetRateLimit() []RateLimit { 2223 return r.RateLimits 2224} 2225 2226func (r *PostLocalRes) SetRateLimits(rl []RateLimit) { 2227 r.RateLimits = rl 2228} 2229 2230func (r *GetConversationForCLILocalRes) GetRateLimit() []RateLimit { 2231 return r.RateLimits 2232} 2233 2234func (r *GetConversationForCLILocalRes) SetRateLimits(rl []RateLimit) { 2235 r.RateLimits = rl 2236} 2237 2238func (r *PostLocalNonblockRes) GetRateLimit() []RateLimit { 2239 return r.RateLimits 2240} 2241 2242func (r *PostLocalNonblockRes) SetRateLimits(rl []RateLimit) { 2243 r.RateLimits = rl 2244} 2245 2246func (r *DownloadAttachmentLocalRes) GetRateLimit() []RateLimit { 2247 return r.RateLimits 2248} 2249 2250func (r *DownloadAttachmentLocalRes) SetRateLimits(rl []RateLimit) { 2251 r.RateLimits = rl 2252} 2253 2254func (r *DownloadFileAttachmentLocalRes) GetRateLimit() []RateLimit { 2255 return r.RateLimits 2256} 2257 2258func (r *DownloadFileAttachmentLocalRes) SetRateLimits(rl []RateLimit) { 2259 r.RateLimits = rl 2260} 2261 2262func (r *FindConversationsLocalRes) GetRateLimit() []RateLimit { 2263 return r.RateLimits 2264} 2265 2266func (r *FindConversationsLocalRes) SetRateLimits(rl []RateLimit) { 2267 r.RateLimits = rl 2268} 2269 2270func (r *JoinLeaveConversationLocalRes) GetRateLimit() []RateLimit { 2271 return r.RateLimits 2272} 2273 2274func (r *JoinLeaveConversationLocalRes) SetRateLimits(rl []RateLimit) { 2275 r.RateLimits = rl 2276} 2277 2278func (r *PreviewConversationLocalRes) GetRateLimit() []RateLimit { 2279 return r.RateLimits 2280} 2281 2282func (r *PreviewConversationLocalRes) SetRateLimits(rl []RateLimit) { 2283 r.RateLimits = rl 2284} 2285 2286func (r *DeleteConversationLocalRes) GetRateLimit() []RateLimit { 2287 return r.RateLimits 2288} 2289 2290func (r *DeleteConversationLocalRes) SetRateLimits(rl []RateLimit) { 2291 r.RateLimits = rl 2292} 2293 2294func (r *RemoveFromConversationLocalRes) GetRateLimit() []RateLimit { 2295 return r.RateLimits 2296} 2297 2298func (r *RemoveFromConversationLocalRes) SetRateLimits(rl []RateLimit) { 2299 r.RateLimits = rl 2300} 2301 2302func (r *GetTLFConversationsLocalRes) GetRateLimit() []RateLimit { 2303 return r.RateLimits 2304} 2305 2306func (r *GetTLFConversationsLocalRes) SetRateLimits(rl []RateLimit) { 2307 r.RateLimits = rl 2308} 2309 2310func (r *GetChannelMembershipsLocalRes) GetRateLimit() []RateLimit { 2311 return r.RateLimits 2312} 2313 2314func (r *GetChannelMembershipsLocalRes) SetRateLimits(rl []RateLimit) { 2315 r.RateLimits = rl 2316} 2317 2318func (r *GetMutualTeamsLocalRes) GetRateLimit() []RateLimit { 2319 return r.RateLimits 2320} 2321 2322func (r *GetMutualTeamsLocalRes) SetRateLimits(rl []RateLimit) { 2323 r.RateLimits = rl 2324} 2325 2326func (r *SetAppNotificationSettingsLocalRes) GetRateLimit() []RateLimit { 2327 return r.RateLimits 2328} 2329 2330func (r *SetAppNotificationSettingsLocalRes) SetRateLimits(rl []RateLimit) { 2331 r.RateLimits = rl 2332} 2333 2334func (r *SearchRegexpRes) GetRateLimit() []RateLimit { 2335 return r.RateLimits 2336} 2337 2338func (r *SearchRegexpRes) SetRateLimits(rl []RateLimit) { 2339 r.RateLimits = rl 2340} 2341 2342func (r *SearchInboxRes) GetRateLimit() []RateLimit { 2343 return r.RateLimits 2344} 2345 2346func (r *SearchInboxRes) SetRateLimits(rl []RateLimit) { 2347 r.RateLimits = rl 2348} 2349 2350func (r *GetInboxRemoteRes) GetRateLimit() (res []RateLimit) { 2351 if r.RateLimit != nil { 2352 res = []RateLimit{*r.RateLimit} 2353 } 2354 return res 2355} 2356 2357func (r *GetInboxRemoteRes) SetRateLimits(rl []RateLimit) { 2358 if len(rl) > 0 { 2359 r.RateLimit = &rl[0] 2360 } 2361} 2362 2363func (r *GetInboxByTLFIDRemoteRes) GetRateLimit() (res []RateLimit) { 2364 if r.RateLimit != nil { 2365 res = []RateLimit{*r.RateLimit} 2366 } 2367 return res 2368} 2369 2370func (r *GetInboxByTLFIDRemoteRes) SetRateLimits(rl []RateLimit) { 2371 if len(rl) > 0 { 2372 r.RateLimit = &rl[0] 2373 } 2374} 2375 2376func (r *GetThreadRemoteRes) GetRateLimit() (res []RateLimit) { 2377 if r.RateLimit != nil { 2378 res = []RateLimit{*r.RateLimit} 2379 } 2380 return res 2381} 2382 2383func (r *GetThreadRemoteRes) SetRateLimits(rl []RateLimit) { 2384 if len(rl) > 0 { 2385 r.RateLimit = &rl[0] 2386 } 2387} 2388 2389func (r *GetConversationMetadataRemoteRes) GetRateLimit() (res []RateLimit) { 2390 if r.RateLimit != nil { 2391 res = []RateLimit{*r.RateLimit} 2392 } 2393 return res 2394} 2395 2396func (r *GetConversationMetadataRemoteRes) SetRateLimits(rl []RateLimit) { 2397 if len(rl) > 0 { 2398 r.RateLimit = &rl[0] 2399 } 2400} 2401 2402func (r *PostRemoteRes) GetRateLimit() (res []RateLimit) { 2403 if r.RateLimit != nil { 2404 res = []RateLimit{*r.RateLimit} 2405 } 2406 return res 2407} 2408 2409func (r *PostRemoteRes) SetRateLimits(rl []RateLimit) { 2410 if len(rl) > 0 { 2411 r.RateLimit = &rl[0] 2412 } 2413} 2414 2415func (r *NewConversationRemoteRes) GetRateLimit() (res []RateLimit) { 2416 if r.RateLimit != nil { 2417 res = []RateLimit{*r.RateLimit} 2418 } 2419 return res 2420} 2421 2422func (r *NewConversationRemoteRes) SetRateLimits(rl []RateLimit) { 2423 if len(rl) > 0 { 2424 r.RateLimit = &rl[0] 2425 } 2426} 2427 2428func (r *GetMessagesRemoteRes) GetRateLimit() (res []RateLimit) { 2429 if r.RateLimit != nil { 2430 res = []RateLimit{*r.RateLimit} 2431 } 2432 return res 2433} 2434 2435func (r *GetMessagesRemoteRes) SetRateLimits(rl []RateLimit) { 2436 if len(rl) > 0 { 2437 r.RateLimit = &rl[0] 2438 } 2439} 2440 2441func (r *MarkAsReadRes) GetRateLimit() (res []RateLimit) { 2442 if r.RateLimit != nil { 2443 res = []RateLimit{*r.RateLimit} 2444 } 2445 return res 2446} 2447 2448func (r *MarkAsReadRes) SetRateLimits(rl []RateLimit) { 2449 if len(rl) > 0 { 2450 r.RateLimit = &rl[0] 2451 } 2452} 2453 2454func (r *SetConversationStatusRes) GetRateLimit() (res []RateLimit) { 2455 if r.RateLimit != nil { 2456 res = []RateLimit{*r.RateLimit} 2457 } 2458 return res 2459} 2460 2461func (r *SetConversationStatusRes) SetRateLimits(rl []RateLimit) { 2462 if len(rl) > 0 { 2463 r.RateLimit = &rl[0] 2464 } 2465} 2466 2467func (r *GetPublicConversationsRes) GetRateLimit() (res []RateLimit) { 2468 if r.RateLimit != nil { 2469 res = []RateLimit{*r.RateLimit} 2470 } 2471 return res 2472} 2473 2474func (r *GetPublicConversationsRes) SetRateLimits(rl []RateLimit) { 2475 if len(rl) > 0 { 2476 r.RateLimit = &rl[0] 2477 } 2478} 2479 2480func (r *JoinLeaveConversationRemoteRes) GetRateLimit() (res []RateLimit) { 2481 if r.RateLimit != nil { 2482 res = []RateLimit{*r.RateLimit} 2483 } 2484 return res 2485} 2486 2487func (r *JoinLeaveConversationRemoteRes) SetRateLimits(rl []RateLimit) { 2488 if len(rl) > 0 { 2489 r.RateLimit = &rl[0] 2490 } 2491} 2492 2493func (r *DeleteConversationRemoteRes) GetRateLimit() (res []RateLimit) { 2494 if r.RateLimit != nil { 2495 res = []RateLimit{*r.RateLimit} 2496 } 2497 return res 2498} 2499 2500func (r *DeleteConversationRemoteRes) SetRateLimits(rl []RateLimit) { 2501 if len(rl) > 0 { 2502 r.RateLimit = &rl[0] 2503 } 2504} 2505 2506func (r *RemoveFromConversationRemoteRes) GetRateLimit() (res []RateLimit) { 2507 if r.RateLimit != nil { 2508 res = []RateLimit{*r.RateLimit} 2509 } 2510 return res 2511} 2512 2513func (r *RemoveFromConversationRemoteRes) SetRateLimits(rl []RateLimit) { 2514 if len(rl) > 0 { 2515 r.RateLimit = &rl[0] 2516 } 2517} 2518 2519func (r *GetMessageBeforeRes) GetRateLimit() (res []RateLimit) { 2520 if r.RateLimit != nil { 2521 res = []RateLimit{*r.RateLimit} 2522 } 2523 return res 2524} 2525 2526func (r *GetMessageBeforeRes) SetRateLimits(rl []RateLimit) { 2527 if len(rl) > 0 { 2528 r.RateLimit = &rl[0] 2529 } 2530} 2531 2532func (r *GetTLFConversationsRes) GetRateLimit() (res []RateLimit) { 2533 if r.RateLimit != nil { 2534 res = []RateLimit{*r.RateLimit} 2535 } 2536 return res 2537} 2538 2539func (r *GetTLFConversationsRes) SetRateLimits(rl []RateLimit) { 2540 if len(rl) > 0 { 2541 r.RateLimit = &rl[0] 2542 } 2543} 2544 2545func (r *SetAppNotificationSettingsRes) GetRateLimit() (res []RateLimit) { 2546 if r.RateLimit != nil { 2547 res = []RateLimit{*r.RateLimit} 2548 } 2549 return res 2550} 2551 2552func (r *SetAppNotificationSettingsRes) SetRateLimits(rl []RateLimit) { 2553 if len(rl) > 0 { 2554 r.RateLimit = &rl[0] 2555 } 2556} 2557 2558func (r *SetRetentionRes) GetRateLimit() (res []RateLimit) { 2559 if r.RateLimit != nil { 2560 res = []RateLimit{*r.RateLimit} 2561 } 2562 return res 2563} 2564 2565func (r *SetRetentionRes) SetRateLimits(rl []RateLimit) { 2566 if len(rl) > 0 { 2567 r.RateLimit = &rl[0] 2568 } 2569} 2570 2571func (r *LoadGalleryRes) GetRateLimit() []RateLimit { 2572 return r.RateLimits 2573} 2574 2575func (r *LoadGalleryRes) SetRateLimits(rl []RateLimit) { 2576 r.RateLimits = rl 2577} 2578 2579func (r *ListBotCommandsLocalRes) GetRateLimit() []RateLimit { 2580 return r.RateLimits 2581} 2582 2583func (r *ListBotCommandsLocalRes) SetRateLimits(rl []RateLimit) { 2584 r.RateLimits = rl 2585} 2586 2587func (r *PinMessageRes) GetRateLimit() []RateLimit { 2588 return r.RateLimits 2589} 2590 2591func (r *PinMessageRes) SetRateLimits(rl []RateLimit) { 2592 r.RateLimits = rl 2593} 2594 2595func (r *ClearBotCommandsLocalRes) GetRateLimit() []RateLimit { 2596 return r.RateLimits 2597} 2598 2599func (r *ClearBotCommandsLocalRes) SetRateLimits(rl []RateLimit) { 2600 r.RateLimits = rl 2601} 2602 2603func (r *ClearBotCommandsRes) GetRateLimit() (res []RateLimit) { 2604 if r.RateLimit != nil { 2605 res = []RateLimit{*r.RateLimit} 2606 } 2607 return res 2608} 2609 2610func (r *ClearBotCommandsRes) SetRateLimits(rl []RateLimit) { 2611 if len(rl) > 0 { 2612 r.RateLimit = &rl[0] 2613 } 2614} 2615 2616func (r *AdvertiseBotCommandsLocalRes) GetRateLimit() []RateLimit { 2617 return r.RateLimits 2618} 2619 2620func (r *AdvertiseBotCommandsLocalRes) SetRateLimits(rl []RateLimit) { 2621 r.RateLimits = rl 2622} 2623 2624func (r *AdvertiseBotCommandsRes) GetRateLimit() (res []RateLimit) { 2625 if r.RateLimit != nil { 2626 res = []RateLimit{*r.RateLimit} 2627 } 2628 return res 2629} 2630 2631func (r *AdvertiseBotCommandsRes) SetRateLimits(rl []RateLimit) { 2632 if len(rl) > 0 { 2633 r.RateLimit = &rl[0] 2634 } 2635} 2636 2637func (r *GetBotInfoRes) GetRateLimit() (res []RateLimit) { 2638 if r.RateLimit != nil { 2639 res = []RateLimit{*r.RateLimit} 2640 } 2641 return res 2642} 2643 2644func (r *GetBotInfoRes) SetRateLimits(rl []RateLimit) { 2645 if len(rl) > 0 { 2646 r.RateLimit = &rl[0] 2647 } 2648} 2649 2650func (r *NewConversationsLocalRes) GetRateLimit() (res []RateLimit) { 2651 return r.RateLimits 2652} 2653 2654func (r *NewConversationsLocalRes) SetRateLimits(rl []RateLimit) { 2655 r.RateLimits = rl 2656} 2657 2658func (r *GetDefaultTeamChannelsLocalRes) GetRateLimit() (res []RateLimit) { 2659 if r.RateLimit != nil { 2660 res = []RateLimit{*r.RateLimit} 2661 } 2662 return res 2663} 2664 2665func (r *GetDefaultTeamChannelsLocalRes) SetRateLimits(rl []RateLimit) { 2666 if len(rl) > 0 { 2667 r.RateLimit = &rl[0] 2668 } 2669} 2670 2671func (r *SetDefaultTeamChannelsLocalRes) GetRateLimit() (res []RateLimit) { 2672 if r.RateLimit != nil { 2673 res = []RateLimit{*r.RateLimit} 2674 } 2675 return res 2676} 2677 2678func (r *SetDefaultTeamChannelsLocalRes) SetRateLimits(rl []RateLimit) { 2679 if len(rl) > 0 { 2680 r.RateLimit = &rl[0] 2681 } 2682} 2683 2684func (r *AddEmojiRes) GetRateLimit() (res []RateLimit) { 2685 if r.RateLimit != nil { 2686 res = []RateLimit{*r.RateLimit} 2687 } 2688 return res 2689} 2690 2691func (r *AddEmojiRes) SetRateLimits(rl []RateLimit) { 2692 if len(rl) > 0 { 2693 r.RateLimit = &rl[0] 2694 } 2695} 2696 2697func (r *AddEmojisRes) GetRateLimit() (res []RateLimit) { 2698 if r.RateLimit != nil { 2699 res = []RateLimit{*r.RateLimit} 2700 } 2701 return res 2702} 2703 2704func (r *AddEmojisRes) SetRateLimits(rl []RateLimit) { 2705 if len(rl) > 0 { 2706 r.RateLimit = &rl[0] 2707 } 2708} 2709 2710func (r *AddEmojiAliasRes) GetRateLimit() (res []RateLimit) { 2711 if r.RateLimit != nil { 2712 res = []RateLimit{*r.RateLimit} 2713 } 2714 return res 2715} 2716 2717func (r *AddEmojiAliasRes) SetRateLimits(rl []RateLimit) { 2718 if len(rl) > 0 { 2719 r.RateLimit = &rl[0] 2720 } 2721} 2722 2723func (r *RemoveEmojiRes) GetRateLimit() (res []RateLimit) { 2724 if r.RateLimit != nil { 2725 res = []RateLimit{*r.RateLimit} 2726 } 2727 return res 2728} 2729 2730func (r *RemoveEmojiRes) SetRateLimits(rl []RateLimit) { 2731 if len(rl) > 0 { 2732 r.RateLimit = &rl[0] 2733 } 2734} 2735 2736func (r *UserEmojiRes) GetRateLimit() (res []RateLimit) { 2737 if r.RateLimit != nil { 2738 res = []RateLimit{*r.RateLimit} 2739 } 2740 return res 2741} 2742 2743func (r *UserEmojiRes) SetRateLimits(rl []RateLimit) { 2744 if len(rl) > 0 { 2745 r.RateLimit = &rl[0] 2746 } 2747} 2748 2749func (i EphemeralPurgeInfo) IsNil() bool { 2750 return i.IsActive == false && i.NextPurgeTime == 0 && i.MinUnexplodedID <= 1 2751} 2752 2753func (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 2758func (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 2765func (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 2774func (r MessageReaction) Eq(o MessageReaction) bool { 2775 return r.Body == o.Body && r.MessageID == o.MessageID 2776} 2777 2778func (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 2789func (s *ConversationSettings) IsNil() bool { 2790 return s == nil || s.MinWriterRoleInfo == nil 2791} 2792 2793func (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 2821func (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 2829func (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 2846func (h *ChatSearchInboxHit) Size() int { 2847 if h == nil { 2848 return 0 2849 } 2850 return len(h.Hits) 2851} 2852 2853func (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 2869func (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 2883func yieldStr(s *string) string { 2884 if s == nil { 2885 return "" 2886 } 2887 return *s 2888} 2889 2890func (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 2897Url: %s 2898SiteName: %s 2899PublishTime: %s 2900Description: %s 2901ImageUrl: %s 2902Video: %s 2903FaviconUrl: %s`, g.Title, g.Url, g.SiteName, publishTime, yieldStr(g.Description), 2904 yieldStr(g.ImageUrl), g.Video, yieldStr(g.FaviconUrl)) 2905} 2906 2907func (g UnfurlGiphyRaw) UnsafeDebugString() string { 2908 2909 return fmt.Sprintf(`GIPHY SPECIAL 2910FaviconUrl: %s 2911ImageUrl: %s 2912Video: %s`, yieldStr(g.FaviconUrl), yieldStr(g.ImageUrl), g.Video) 2913} 2914 2915func (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 2919func NewUnfurlSettings() UnfurlSettings { 2920 return UnfurlSettings{ 2921 Mode: UnfurlMode_WHITELISTED, 2922 Whitelist: make(map[string]bool), 2923 } 2924} 2925 2926func 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` 2940func (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 default: 2953 return "" 2954 } 2955} 2956 2957func (g GlobalAppNotificationSetting) FlagName() string { 2958 switch g { 2959 case GlobalAppNotificationSetting_NEWMESSAGES: 2960 return "new-messages" 2961 case GlobalAppNotificationSetting_PLAINTEXTDESKTOP: 2962 return "plaintext-desktop" 2963 case GlobalAppNotificationSetting_PLAINTEXTMOBILE: 2964 return "plaintext-mobile" 2965 case GlobalAppNotificationSetting_DEFAULTSOUNDMOBILE: 2966 return "default-sound-mobile" 2967 case GlobalAppNotificationSetting_DISABLETYPING: 2968 return "disable-typing" 2969 default: 2970 return "" 2971 } 2972} 2973 2974func (m MessageSystemChangeRetention) String() string { 2975 var appliesTo string 2976 switch m.MembersType { 2977 case ConversationMembersType_TEAM: 2978 if m.IsTeam { 2979 appliesTo = "team" 2980 } else { 2981 appliesTo = "channel" 2982 } 2983 default: 2984 appliesTo = "conversation" 2985 } 2986 var inheritDescription string 2987 if m.IsInherit { 2988 inheritDescription = " to inherit from the team policy" 2989 } 2990 2991 format := "%s changed the %s retention policy%s. %s" 2992 summary := m.Policy.HumanSummary() 2993 return fmt.Sprintf(format, m.User, appliesTo, inheritDescription, summary) 2994} 2995 2996func (m MessageSystemBulkAddToConv) String() string { 2997 prefix := "Added %s to the conversation" 2998 var suffix string 2999 switch len(m.Usernames) { 3000 case 0: 3001 return "" 3002 case 1: 3003 suffix = m.Usernames[0] 3004 case 2: 3005 suffix = fmt.Sprintf("%s and %s", m.Usernames[0], m.Usernames[1]) 3006 default: 3007 suffix = fmt.Sprintf("%s and %d others", m.Usernames[0], len(m.Usernames)-1) 3008 } 3009 return fmt.Sprintf(prefix, suffix) 3010} 3011 3012func withDeterminer(s string) string { 3013 r, size := utf8.DecodeRuneInString(s) 3014 if size == 0 || r == utf8.RuneError { 3015 return "a " + s 3016 } 3017 if strings.Contains("aeiou", string(r)) { 3018 return "an " + s 3019 } 3020 return "a " + s 3021} 3022 3023func (m MessageSystem) String() string { 3024 typ, err := m.SystemType() 3025 if err != nil { 3026 return "" 3027 } 3028 switch typ { 3029 case MessageSystemType_ADDEDTOTEAM: 3030 output := fmt.Sprintf("Added @%s to the team", m.Addedtoteam().Addee) 3031 if role := m.Addedtoteam().Role; role != keybase1.TeamRole_NONE { 3032 output += fmt.Sprintf(" as %v", withDeterminer(role.HumanString())) 3033 } 3034 return output 3035 case MessageSystemType_INVITEADDEDTOTEAM: 3036 var roleText string 3037 if role := m.Inviteaddedtoteam().Role; role != keybase1.TeamRole_NONE { 3038 roleText = fmt.Sprintf(" as %v", withDeterminer(role.HumanString())) 3039 } 3040 output := fmt.Sprintf("Added @%s to the team (invited by @%s%s)", 3041 m.Inviteaddedtoteam().Invitee, m.Inviteaddedtoteam().Inviter, roleText) 3042 return output 3043 case MessageSystemType_COMPLEXTEAM: 3044 return fmt.Sprintf("%s is now a 'big' team with multiple channels", m.Complexteam().Team) 3045 case MessageSystemType_CREATETEAM: 3046 return fmt.Sprintf("@%s created the team %s", m.Createteam().Creator, m.Createteam().Team) 3047 case MessageSystemType_GITPUSH: 3048 body := m.Gitpush() 3049 switch body.PushType { 3050 case keybase1.GitPushType_CREATEREPO: 3051 return fmt.Sprintf("git @%s created the repo %s", body.Pusher, body.RepoName) 3052 case keybase1.GitPushType_RENAMEREPO: 3053 return fmt.Sprintf("git @%s changed the name of the repo %s to %s", body.Pusher, body.PreviousRepoName, body.RepoName) 3054 default: 3055 total := keybase1.TotalNumberOfCommits(body.Refs) 3056 names := keybase1.RefNames(body.Refs) 3057 return fmt.Sprintf("git (%s) @%s pushed %d commits to %s", body.RepoName, 3058 body.Pusher, total, names) 3059 } 3060 case MessageSystemType_CHANGEAVATAR: 3061 return fmt.Sprintf("@%s changed team avatar", m.Changeavatar().User) 3062 case MessageSystemType_CHANGERETENTION: 3063 return m.Changeretention().String() 3064 case MessageSystemType_BULKADDTOCONV: 3065 return m.Bulkaddtoconv().String() 3066 case MessageSystemType_SBSRESOLVE: 3067 body := m.Sbsresolve() 3068 switch body.AssertionService { 3069 case "phone": 3070 return fmt.Sprintf("@%s verified their phone number %s and joined"+ 3071 " the conversation", body.Prover, body.AssertionUsername) 3072 case "email": 3073 return fmt.Sprintf("@%s verified their email address %s and joined"+ 3074 " the conversation", body.Prover, body.AssertionUsername) 3075 } 3076 return fmt.Sprintf("@%s proved they are %s on %s and joined"+ 3077 " the conversation", body.Prover, body.AssertionUsername, 3078 body.AssertionService) 3079 case MessageSystemType_NEWCHANNEL: 3080 body := m.Newchannel() 3081 if len(body.ConvIDs) > 1 { 3082 return fmt.Sprintf("@%s created #%s and %d other new channels", 3083 body.Creator, body.NameAtCreation, len(body.ConvIDs)-1) 3084 } 3085 return fmt.Sprintf("@%s created a new channel #%s", 3086 body.Creator, body.NameAtCreation) 3087 default: 3088 return "" 3089 } 3090} 3091 3092func (m MessageHeadline) String() string { 3093 if m.Headline == "" { 3094 return "cleared the channel description" 3095 } 3096 return fmt.Sprintf("set the channel description: %v", m.Headline) 3097} 3098 3099func isZero(v []byte) bool { 3100 for _, b := range v { 3101 if b != 0 { 3102 return false 3103 } 3104 } 3105 return true 3106} 3107 3108func MakeFlipGameID(s string) (FlipGameID, error) { return hex.DecodeString(s) } 3109func (g FlipGameID) String() string { return hex.EncodeToString(g) } 3110func (g FlipGameID) FlipGameIDStr() FlipGameIDStr { return FlipGameIDStr(g.String()) } 3111func (g FlipGameID) Eq(h FlipGameID) bool { return hmac.Equal(g[:], h[:]) } 3112func (g FlipGameID) IsZero() bool { return isZero(g[:]) } 3113func (g FlipGameID) Check() bool { return g != nil && !g.IsZero() } 3114 3115func (o *SenderSendOptions) GetJoinMentionsAs() *ConversationMemberStatus { 3116 if o == nil { 3117 return nil 3118 } 3119 return o.JoinMentionsAs 3120} 3121 3122func (c Coordinate) IsZero() bool { 3123 return c.Lat == 0 && c.Lon == 0 3124} 3125 3126func (c Coordinate) Eq(o Coordinate) bool { 3127 return c.Lat == o.Lat && c.Lon == o.Lon 3128} 3129 3130type safeCoordinate struct { 3131 Lat float64 `codec:"lat" json:"lat"` 3132 Lon float64 `codec:"lon" json:"lon"` 3133 Accuracy float64 `codec:"accuracy" json:"accuracy"` 3134} 3135 3136func (c Coordinate) MarshalJSON() ([]byte, error) { 3137 var safe safeCoordinate 3138 safe.Lat = c.Lat 3139 safe.Lon = c.Lon 3140 safe.Accuracy = c.Accuracy 3141 if math.IsNaN(safe.Lat) { 3142 safe.Lat = 0 3143 } 3144 if math.IsNaN(safe.Lon) { 3145 safe.Lon = 0 3146 } 3147 if math.IsNaN(safe.Accuracy) { 3148 safe.Accuracy = 0 3149 } 3150 return json.Marshal(safe) 3151} 3152 3153// Incremented if the client hash algorithm changes. If this value is changed 3154// be sure to add a case in the BotInfo.Hash() function. 3155const ClientBotInfoHashVers BotInfoHashVers = 2 3156 3157// Incremented if the server sends down bad data and needs to bust client 3158// caches. 3159const ServerBotInfoHashVers BotInfoHashVers = 1 3160 3161func (b BotInfo) Hash() BotInfoHash { 3162 hash := sha256Pool.Get().(hash.Hash) 3163 defer sha256Pool.Put(hash) 3164 hash.Reset() 3165 3166 // Always hash in the server/client version. 3167 hash.Write([]byte(strconv.FormatUint(uint64(b.ServerHashVers), 10))) 3168 hash.Write([]byte(strconv.FormatUint(uint64(b.ClientHashVers), 10))) 3169 3170 // This should cover all cases from 0..DefaultBotInfoHashVers. If 3171 // incrementing DefaultBotInfoHashVers be sure to add a case here. 3172 switch b.ClientHashVers { 3173 case 0, 1: 3174 b.hashV1(hash) 3175 case 2: 3176 b.hashV2(hash) 3177 default: 3178 // Every valid client version should be specifically handled, unit 3179 // tests verify that we have a non-empty hash output. 3180 hash.Reset() 3181 } 3182 return BotInfoHash(hash.Sum(nil)) 3183} 3184 3185func (b BotInfo) hashV1(hash hash.Hash) { 3186 sort.Slice(b.CommandConvs, func(i, j int) bool { 3187 ikey := b.CommandConvs[i].Uid.String() + b.CommandConvs[i].ConvID.String() 3188 jkey := b.CommandConvs[j].Uid.String() + b.CommandConvs[j].ConvID.String() 3189 return ikey < jkey 3190 }) 3191 for _, cconv := range b.CommandConvs { 3192 hash.Write(cconv.ConvID) 3193 hash.Write(cconv.Uid) 3194 hash.Write([]byte(strconv.FormatUint(uint64(cconv.UntrustedTeamRole), 10))) 3195 hash.Write([]byte(strconv.FormatUint(uint64(cconv.Vers), 10))) 3196 } 3197} 3198 3199func (b BotInfo) hashV2(hash hash.Hash) { 3200 sort.Slice(b.CommandConvs, func(i, j int) bool { 3201 ikey := b.CommandConvs[i].Uid.String() + b.CommandConvs[i].ConvID.String() 3202 jkey := b.CommandConvs[j].Uid.String() + b.CommandConvs[j].ConvID.String() 3203 return ikey < jkey 3204 }) 3205 for _, cconv := range b.CommandConvs { 3206 hash.Write(cconv.ConvID) 3207 hash.Write(cconv.Uid) 3208 hash.Write([]byte(strconv.FormatUint(uint64(cconv.UntrustedTeamRole), 10))) 3209 hash.Write([]byte(strconv.FormatUint(uint64(cconv.Vers), 10))) 3210 hash.Write([]byte(strconv.FormatUint(uint64(cconv.Typ), 10))) 3211 } 3212} 3213 3214func (b BotInfoHash) Eq(h BotInfoHash) bool { 3215 return bytes.Equal(b, h) 3216} 3217 3218func (p AdvertiseCommandsParam) ToRemote(cmdConvID ConversationID, tlfID *TLFID, adConvID *ConversationID) (res RemoteBotCommandsAdvertisement, err error) { 3219 switch p.Typ { 3220 case BotCommandsAdvertisementTyp_PUBLIC: 3221 if tlfID != nil { 3222 return res, errors.New("TLFID specified for public advertisement") 3223 } else if adConvID != nil { 3224 return res, errors.New("ConvID specified for public advertisement") 3225 } 3226 return NewRemoteBotCommandsAdvertisementWithPublic(RemoteBotCommandsAdvertisementPublic{ 3227 ConvID: cmdConvID, 3228 }), nil 3229 case BotCommandsAdvertisementTyp_TLFID_CONVS: 3230 if tlfID == nil { 3231 return res, errors.New("no TLFID specified") 3232 } else if adConvID != nil { 3233 return res, errors.New("ConvID specified") 3234 } 3235 return NewRemoteBotCommandsAdvertisementWithTlfidConvs(RemoteBotCommandsAdvertisementTLFID{ 3236 ConvID: cmdConvID, 3237 TlfID: *tlfID, 3238 }), nil 3239 case BotCommandsAdvertisementTyp_TLFID_MEMBERS: 3240 if tlfID == nil { 3241 return res, errors.New("no TLFID specified") 3242 } else if adConvID != nil { 3243 return res, errors.New("ConvID specified") 3244 } 3245 return NewRemoteBotCommandsAdvertisementWithTlfidMembers(RemoteBotCommandsAdvertisementTLFID{ 3246 ConvID: cmdConvID, 3247 TlfID: *tlfID, 3248 }), nil 3249 case BotCommandsAdvertisementTyp_CONV: 3250 if tlfID != nil { 3251 return res, errors.New("TLFID specified") 3252 } else if adConvID == nil { 3253 return res, errors.New("no ConvID specified") 3254 } 3255 return NewRemoteBotCommandsAdvertisementWithConv(RemoteBotCommandsAdvertisementConv{ 3256 ConvID: cmdConvID, 3257 AdvertiseConvID: *adConvID, 3258 }), nil 3259 default: 3260 return res, errors.New("unknown bot advertisement typ") 3261 } 3262} 3263 3264func (p ClearBotCommandsFilter) ToRemote(tlfID *TLFID, convID *ConversationID) (res RemoteClearBotCommandsFilter, err error) { 3265 switch p.Typ { 3266 case BotCommandsAdvertisementTyp_PUBLIC: 3267 if tlfID != nil { 3268 return res, errors.New("TLFID specified for public advertisement") 3269 } else if convID != nil { 3270 return res, errors.New("ConvID specified for public advertisement") 3271 } 3272 return NewRemoteClearBotCommandsFilterWithPublic(RemoteClearBotCommandsFilterPublic{}), nil 3273 case BotCommandsAdvertisementTyp_TLFID_CONVS: 3274 if tlfID == nil { 3275 return res, errors.New("no TLFID specified") 3276 } else if convID != nil { 3277 return res, errors.New("ConvID specified") 3278 } 3279 return NewRemoteClearBotCommandsFilterWithTlfidConvs(RemoteClearBotCommandsFilterTLFID{ 3280 TlfID: *tlfID, 3281 }), nil 3282 case BotCommandsAdvertisementTyp_TLFID_MEMBERS: 3283 if tlfID == nil { 3284 return res, errors.New("no TLFID specified") 3285 } else if convID != nil { 3286 return res, errors.New("ConvID specified") 3287 } 3288 return NewRemoteClearBotCommandsFilterWithTlfidMembers(RemoteClearBotCommandsFilterTLFID{ 3289 TlfID: *tlfID, 3290 }), nil 3291 case BotCommandsAdvertisementTyp_CONV: 3292 if tlfID != nil { 3293 return res, errors.New("TLFID specified") 3294 } else if convID == nil { 3295 return res, errors.New("no ConvID specified") 3296 } 3297 return NewRemoteClearBotCommandsFilterWithConv(RemoteClearBotCommandsFilterConv{ 3298 ConvID: *convID, 3299 }), nil 3300 default: 3301 return res, errors.New("unknown bot advertisement typ") 3302 } 3303} 3304 3305func GetAdvertTyp(typ string) (BotCommandsAdvertisementTyp, error) { 3306 switch typ { 3307 case "public": 3308 return BotCommandsAdvertisementTyp_PUBLIC, nil 3309 case "teamconvs": 3310 return BotCommandsAdvertisementTyp_TLFID_CONVS, nil 3311 case "teammembers": 3312 return BotCommandsAdvertisementTyp_TLFID_MEMBERS, nil 3313 case "conv": 3314 return BotCommandsAdvertisementTyp_CONV, nil 3315 default: 3316 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) 3317 } 3318} 3319 3320func (c UserBotCommandInput) ToOutput(username string) UserBotCommandOutput { 3321 return UserBotCommandOutput{ 3322 Name: c.Name, 3323 Description: c.Description, 3324 Usage: c.Usage, 3325 ExtendedDescription: c.ExtendedDescription, 3326 Username: username, 3327 } 3328} 3329 3330func (r UIInboxReselectInfo) String() string { 3331 newConvStr := "<none>" 3332 if r.NewConvID != nil { 3333 newConvStr = string(*r.NewConvID) 3334 } 3335 return fmt.Sprintf("[oldconv: %s newconv: %s]", r.OldConvID, newConvStr) 3336} 3337 3338func (e OutboxErrorType) IsBadgableError() bool { 3339 switch e { 3340 case OutboxErrorType_MISC, 3341 OutboxErrorType_OFFLINE, 3342 OutboxErrorType_TOOLONG, 3343 OutboxErrorType_EXPIRED, 3344 OutboxErrorType_TOOMANYATTEMPTS, 3345 OutboxErrorType_UPLOADFAILED: 3346 return true 3347 default: 3348 return false 3349 } 3350} 3351 3352func (c UserBotCommandOutput) Matches(text string) bool { 3353 return strings.HasPrefix(text, fmt.Sprintf("!%s", c.Name)) 3354} 3355 3356func (m AssetMetadata) IsType(typ AssetMetadataType) bool { 3357 mtyp, err := m.AssetType() 3358 if err != nil { 3359 return false 3360 } 3361 return mtyp == typ 3362} 3363 3364type safeAssetMetadataImage struct { 3365 Width int `codec:"width" json:"width"` 3366 Height int `codec:"height" json:"height"` 3367 AudioAmps []float64 `codec:"audioAmps" json:"audioAmps"` 3368} 3369 3370func (m AssetMetadataImage) MarshalJSON() ([]byte, error) { 3371 var safe safeAssetMetadataImage 3372 safe.AudioAmps = make([]float64, 0, len(m.AudioAmps)) 3373 for _, amp := range m.AudioAmps { 3374 if math.IsNaN(amp) { 3375 safe.AudioAmps = append(safe.AudioAmps, 0) 3376 } else { 3377 safe.AudioAmps = append(safe.AudioAmps, amp) 3378 } 3379 } 3380 safe.Width = m.Width 3381 safe.Height = m.Height 3382 return json.Marshal(safe) 3383} 3384 3385func (s SnippetDecoration) ToEmoji() string { 3386 switch s { 3387 case SnippetDecoration_PENDING_MESSAGE: 3388 return ":outbox_tray:" 3389 case SnippetDecoration_FAILED_PENDING_MESSAGE: 3390 return ":warning:" 3391 case SnippetDecoration_EXPLODING_MESSAGE: 3392 return ":bomb:" 3393 case SnippetDecoration_EXPLODED_MESSAGE: 3394 return ":boom:" 3395 case SnippetDecoration_AUDIO_ATTACHMENT: 3396 return ":loud_sound:" 3397 case SnippetDecoration_VIDEO_ATTACHMENT: 3398 return ":film_frames:" 3399 case SnippetDecoration_PHOTO_ATTACHMENT: 3400 return ":camera:" 3401 case SnippetDecoration_FILE_ATTACHMENT: 3402 return ":file_folder:" 3403 case SnippetDecoration_STELLAR_RECEIVED: 3404 return ":bank:" 3405 case SnippetDecoration_STELLAR_SENT: 3406 return ":money_with_wings:" 3407 case SnippetDecoration_PINNED_MESSAGE: 3408 return ":pushpin:" 3409 default: 3410 return "" 3411 } 3412} 3413 3414func (r EmojiRemoteSource) IsMessage() bool { 3415 typ, err := r.Typ() 3416 if err != nil { 3417 return false 3418 } 3419 return typ == EmojiRemoteSourceTyp_MESSAGE 3420} 3421 3422func (r EmojiRemoteSource) IsStockAlias() bool { 3423 typ, err := r.Typ() 3424 if err != nil { 3425 return false 3426 } 3427 return typ == EmojiRemoteSourceTyp_STOCKALIAS 3428} 3429 3430func (r EmojiRemoteSource) IsAlias() bool { 3431 return r.IsStockAlias() || (r.IsMessage() && r.Message().IsAlias) 3432} 3433 3434func (r EmojiLoadSource) IsHTTPSrv() bool { 3435 typ, err := r.Typ() 3436 if err != nil { 3437 return false 3438 } 3439 return typ == EmojiLoadSourceTyp_HTTPSRV 3440} 3441 3442func TeamToChatMemberDetails(teamMembers []keybase1.TeamMemberDetails) (chatMembers []ChatMemberDetails) { 3443 chatMembers = make([]ChatMemberDetails, len(teamMembers)) 3444 for i, teamMember := range teamMembers { 3445 chatMembers[i] = ChatMemberDetails{ 3446 Uid: teamMember.Uv.Uid, 3447 Username: teamMember.Username, 3448 FullName: teamMember.FullName, 3449 } 3450 } 3451 return chatMembers 3452} 3453 3454func TeamToChatMembersDetails(details keybase1.TeamMembersDetails) ChatMembersDetails { 3455 return ChatMembersDetails{ 3456 Owners: TeamToChatMemberDetails(details.Owners), 3457 Admins: TeamToChatMemberDetails(details.Admins), 3458 Writers: TeamToChatMemberDetails(details.Writers), 3459 Readers: TeamToChatMemberDetails(details.Readers), 3460 Bots: TeamToChatMemberDetails(details.Bots), 3461 RestrictedBots: TeamToChatMemberDetails(details.RestrictedBots), 3462 } 3463} 3464