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