github.com/status-im/status-go@v1.1.0/protocol/common/message.go (about) 1 package common 2 3 import ( 4 "crypto/ecdsa" 5 "encoding/base64" 6 "encoding/hex" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "strings" 13 "unicode" 14 "unicode/utf8" 15 16 "github.com/golang/protobuf/proto" 17 18 "github.com/status-im/markdown" 19 "github.com/status-im/markdown/ast" 20 21 accountJson "github.com/status-im/status-go/account/json" 22 "github.com/status-im/status-go/eth-node/crypto" 23 "github.com/status-im/status-go/images" 24 "github.com/status-im/status-go/protocol/audio" 25 "github.com/status-im/status-go/protocol/protobuf" 26 ) 27 28 // QuotedMessage contains the original text of the message replied to 29 type QuotedMessage struct { 30 ID string `json:"id"` 31 ContentType int64 `json:"contentType"` 32 // From is a public key of the author of the message. 33 From string `json:"from"` 34 Text string `json:"text"` 35 ParsedText json.RawMessage `json:"parsedText,omitempty"` 36 AlbumImages json.RawMessage `json:"albumImages,omitempty"` 37 AlbumImagesCount int64 `json:"albumImagesCount"` 38 // ImageLocalURL is the local url of the image 39 ImageLocalURL string `json:"image,omitempty"` 40 // AudioLocalURL is the local url of the audio 41 AudioLocalURL string `json:"audio,omitempty"` 42 43 HasSticker bool `json:"sticker,omitempty"` 44 // CommunityID is the id of the community advertised 45 CommunityID string `json:"communityId,omitempty"` 46 47 Deleted bool `json:"deleted,omitempty"` 48 49 DeletedForMe bool `json:"deletedForMe,omitempty"` 50 51 DiscordMessage *protobuf.DiscordMessage `json:"discordMessage,omitempty"` 52 BridgeMessage *protobuf.BridgeMessage `json:"bridgeMessage,omitempty"` 53 } 54 55 type CommandState int 56 57 const ( 58 CommandStateRequestAddressForTransaction CommandState = iota + 1 59 CommandStateRequestAddressForTransactionDeclined 60 CommandStateRequestAddressForTransactionAccepted 61 CommandStateRequestTransaction 62 CommandStateRequestTransactionDeclined 63 CommandStateTransactionPending 64 CommandStateTransactionSent 65 ) 66 67 type ContactRequestState int 68 69 const ( 70 ContactRequestStatePending ContactRequestState = iota + 1 71 ContactRequestStateAccepted 72 ContactRequestStateDismissed 73 ) 74 75 type ContactVerificationState int 76 77 const ( 78 ContactVerificationStatePending ContactVerificationState = iota + 1 79 ContactVerificationStateAccepted 80 ContactVerificationStateDeclined 81 ContactVerificationStateTrusted 82 ContactVerificationStateUntrustworthy 83 ContactVerificationStateCanceled 84 ) 85 86 const EveryoneMentionTag = "0x00001" 87 88 type CommandParameters struct { 89 // ID is the ID of the initial message 90 ID string `json:"id"` 91 // From is the address we are sending the command from 92 From string `json:"from"` 93 // Address is the address sent with the command 94 Address string `json:"address"` 95 // Contract is the contract address for ERC20 tokens 96 Contract string `json:"contract"` 97 // Value is the value as a string sent 98 Value string `json:"value"` 99 // TransactionHash is the hash of the transaction 100 TransactionHash string `json:"transactionHash"` 101 // CommandState is the state of the command 102 CommandState CommandState `json:"commandState"` 103 // The Signature of the pk-bytes+transaction-hash from the wallet 104 // address originating 105 Signature []byte `json:"signature"` 106 } 107 108 // GapParameters is the From and To indicating the missing period in chat history 109 type GapParameters struct { 110 From uint32 `json:"from,omitempty"` 111 To uint32 `json:"to,omitempty"` 112 } 113 114 func (c *CommandParameters) IsTokenTransfer() bool { 115 return len(c.Contract) != 0 116 } 117 118 const ( 119 OutgoingStatusSending = "sending" 120 OutgoingStatusSent = "sent" 121 OutgoingStatusDelivered = "delivered" 122 ) 123 124 type Messages []*Message 125 126 func (m Messages) GetClock(i int) uint64 { 127 return m[i].Clock 128 } 129 130 // Message represents a message record in the database, 131 // more specifically in user_messages table. 132 type Message struct { 133 *protobuf.ChatMessage 134 135 // ID calculated as keccak256(compressedAuthorPubKey, data) where data is unencrypted payload. 136 ID string `json:"id"` 137 // WhisperTimestamp is a timestamp of a Whisper envelope. 138 WhisperTimestamp uint64 `json:"whisperTimestamp"` 139 // From is a public key of the author of the message. 140 From string `json:"from"` 141 // Random 3 words name 142 Alias string `json:"alias"` 143 // Identicon of the author 144 Identicon string `json:"identicon"` 145 // The chat id to be stored locally 146 LocalChatID string `json:"localChatId"` 147 // Seen set to true when user have read this message already 148 Seen bool `json:"seen"` 149 OutgoingStatus string `json:"outgoingStatus,omitempty"` 150 151 QuotedMessage *QuotedMessage `json:"quotedMessage"` 152 153 // CommandParameters is the parameters sent with the message 154 CommandParameters *CommandParameters `json:"commandParameters"` 155 156 // GapParameters is the value from/to related to the gap 157 GapParameters *GapParameters `json:"gapParameters,omitempty"` 158 159 // Computed fields 160 // RTL is whether this is a right-to-left message (arabic/hebrew script etc) 161 RTL bool `json:"rtl"` 162 // ParsedText is the parsed markdown for displaying 163 ParsedText []byte `json:"parsedText,omitempty"` 164 // ParsedTextAst is the ast of the parsed text 165 ParsedTextAst *ast.Node `json:"-"` 166 // LineCount is the count of newlines in the message 167 LineCount int `json:"lineCount"` 168 // Base64Image is the converted base64 image 169 Base64Image string `json:"image,omitempty"` 170 // ImagePath is the path of the image to be sent 171 ImagePath string `json:"imagePath,omitempty"` 172 // Base64Audio is the converted base64 audio 173 Base64Audio string `json:"audio,omitempty"` 174 // AudioPath is the path of the audio to be sent 175 AudioPath string `json:"audioPath,omitempty"` 176 // ImageLocalURL is the local url of the image 177 ImageLocalURL string `json:"imageLocalUrl,omitempty"` 178 // AudioLocalURL is the local url of the audio 179 AudioLocalURL string `json:"audioLocalUrl,omitempty"` 180 // StickerLocalURL is the local url of the sticker 181 StickerLocalURL string `json:"stickerLocalUrl,omitempty"` 182 183 // CommunityID is the id of the community to advertise 184 CommunityID string `json:"communityId,omitempty"` 185 186 // Replace indicates that this is a replacement of a message 187 // that has been updated 188 Replace string `json:"replace,omitempty"` 189 New bool `json:"new,omitempty"` 190 191 SigPubKey *ecdsa.PublicKey `json:"-"` 192 193 // Mentions is an array of mentions for a given message 194 Mentions []string 195 196 // Mentioned is whether the user is mentioned in the message 197 Mentioned bool `json:"mentioned"` 198 199 // Replied is whether the user is replied to in the message 200 Replied bool `json:"replied"` 201 202 // Links is an array of links within given message 203 Links []string 204 LinkPreviews []LinkPreview `json:"linkPreviews"` 205 StatusLinkPreviews []StatusLinkPreview `json:"statusLinkPreviews"` 206 207 // EditedAt indicates the clock value it was edited 208 EditedAt uint64 `json:"editedAt"` 209 210 // Deleted indicates if a message was deleted 211 Deleted bool `json:"deleted"` 212 213 DeletedBy string `json:"deletedBy,omitempty"` 214 215 DeletedForMe bool `json:"deletedForMe"` 216 217 // ContactRequestState is the state of the contact request message 218 ContactRequestState ContactRequestState `json:"contactRequestState,omitempty"` 219 220 // ContactVerificationState is the state of the identity verification process 221 ContactVerificationState ContactVerificationState `json:"contactVerificationState,omitempty"` 222 223 DiscordMessage *protobuf.DiscordMessage `json:"discordMessage,omitempty"` 224 } 225 226 func (m *Message) MarshalJSON() ([]byte, error) { 227 type StickerAlias struct { 228 Hash string `json:"hash"` 229 Pack int32 `json:"pack"` 230 URL string `json:"url"` 231 } 232 233 if m.ChatMessage == nil { 234 m.ChatMessage = &protobuf.ChatMessage{} 235 } 236 237 type MessageStructType struct { 238 ID string `json:"id"` 239 WhisperTimestamp uint64 `json:"whisperTimestamp"` 240 From string `json:"from"` 241 Alias string `json:"alias"` 242 Identicon string `json:"identicon"` 243 Seen bool `json:"seen"` 244 OutgoingStatus string `json:"outgoingStatus,omitempty"` 245 QuotedMessage *QuotedMessage `json:"quotedMessage"` 246 RTL bool `json:"rtl"` 247 ParsedText json.RawMessage `json:"parsedText,omitempty"` 248 LineCount int `json:"lineCount"` 249 Text string `json:"text"` 250 ChatID string `json:"chatId"` 251 LocalChatID string `json:"localChatId"` 252 Clock uint64 `json:"clock"` 253 Replace string `json:"replace"` 254 ResponseTo string `json:"responseTo"` 255 New bool `json:"new,omitempty"` 256 EnsName string `json:"ensName"` 257 DisplayName string `json:"displayName"` 258 Image string `json:"image,omitempty"` 259 AlbumID string `json:"albumId,omitempty"` 260 ImageWidth uint32 `json:"imageWidth,omitempty"` 261 ImageHeight uint32 `json:"imageHeight,omitempty"` 262 AlbumImagesCount uint32 `json:"albumImagesCount,omitempty"` 263 Audio string `json:"audio,omitempty"` 264 AudioDurationMs uint64 `json:"audioDurationMs,omitempty"` 265 CommunityID string `json:"communityId,omitempty"` 266 Sticker *StickerAlias `json:"sticker,omitempty"` 267 CommandParameters *CommandParameters `json:"commandParameters,omitempty"` 268 GapParameters *GapParameters `json:"gapParameters,omitempty"` 269 Timestamp uint64 `json:"timestamp"` 270 ContentType protobuf.ChatMessage_ContentType `json:"contentType"` 271 MessageType protobuf.MessageType `json:"messageType"` 272 Mentions []string `json:"mentions,omitempty"` 273 Mentioned bool `json:"mentioned,omitempty"` 274 Replied bool `json:"replied,omitempty"` 275 Links []string `json:"links,omitempty"` 276 LinkPreviews []LinkPreview `json:"linkPreviews,omitempty"` 277 StatusLinkPreviews []StatusLinkPreview `json:"statusLinkPreviews,omitempty"` 278 EditedAt uint64 `json:"editedAt,omitempty"` 279 Deleted bool `json:"deleted,omitempty"` 280 DeletedBy string `json:"deletedBy,omitempty"` 281 DeletedForMe bool `json:"deletedForMe,omitempty"` 282 ContactRequestState ContactRequestState `json:"contactRequestState,omitempty"` 283 ContactVerificationState ContactVerificationState `json:"contactVerificationState,omitempty"` 284 DiscordMessage *protobuf.DiscordMessage `json:"discordMessage,omitempty"` 285 BridgeMessage *protobuf.BridgeMessage `json:"bridgeMessage,omitempty"` 286 } 287 item := MessageStructType{ 288 ID: m.ID, 289 WhisperTimestamp: m.WhisperTimestamp, 290 From: m.From, 291 Alias: m.Alias, 292 Identicon: m.Identicon, 293 Seen: m.Seen, 294 OutgoingStatus: m.OutgoingStatus, 295 QuotedMessage: m.QuotedMessage, 296 RTL: m.RTL, 297 ParsedText: m.ParsedText, 298 LineCount: m.LineCount, 299 Text: m.Text, 300 Replace: m.Replace, 301 ChatID: m.ChatId, 302 LocalChatID: m.LocalChatID, 303 Clock: m.Clock, 304 ResponseTo: m.ResponseTo, 305 New: m.New, 306 EnsName: m.EnsName, 307 DisplayName: m.DisplayName, 308 Image: m.ImageLocalURL, 309 Audio: m.AudioLocalURL, 310 CommunityID: m.CommunityID, 311 Timestamp: m.Timestamp, 312 ContentType: m.ContentType, 313 Mentions: m.Mentions, 314 Mentioned: m.Mentioned, 315 Replied: m.Replied, 316 Links: m.Links, 317 LinkPreviews: m.LinkPreviews, 318 StatusLinkPreviews: m.StatusLinkPreviews, 319 MessageType: m.MessageType, 320 CommandParameters: m.CommandParameters, 321 GapParameters: m.GapParameters, 322 EditedAt: m.EditedAt, 323 Deleted: m.Deleted, 324 DeletedBy: m.DeletedBy, 325 DeletedForMe: m.DeletedForMe, 326 ContactRequestState: m.ContactRequestState, 327 ContactVerificationState: m.ContactVerificationState, 328 } 329 330 if sticker := m.GetSticker(); sticker != nil { 331 item.Sticker = &StickerAlias{ 332 Pack: sticker.Pack, 333 Hash: sticker.Hash, 334 URL: m.StickerLocalURL, 335 } 336 } 337 338 if audio := m.GetAudio(); audio != nil { 339 item.AudioDurationMs = audio.DurationMs 340 } 341 342 if image := m.GetImage(); image != nil { 343 item.AlbumID = image.AlbumId 344 item.ImageWidth = image.Width 345 item.ImageHeight = image.Height 346 item.AlbumImagesCount = image.AlbumImagesCount 347 } 348 349 if discordMessage := m.GetDiscordMessage(); discordMessage != nil { 350 item.DiscordMessage = discordMessage 351 } 352 353 if bridgeMessage := m.GetBridgeMessage(); bridgeMessage != nil { 354 item.BridgeMessage = bridgeMessage 355 } 356 357 if item.From != "" { 358 ext, err := accountJson.ExtendStructWithPubKeyData(item.From, item) 359 if err != nil { 360 return nil, err 361 } 362 363 return json.Marshal(ext) 364 } 365 366 return json.Marshal(item) 367 } 368 369 func (m *Message) UnmarshalJSON(data []byte) error { 370 type Alias Message 371 aux := struct { 372 *Alias 373 ResponseTo string `json:"responseTo"` 374 EnsName string `json:"ensName"` 375 DisplayName string `json:"displayName"` 376 ChatID string `json:"chatId"` 377 Sticker *protobuf.StickerMessage `json:"sticker"` 378 AudioDurationMs uint64 `json:"audioDurationMs"` 379 ParsedText json.RawMessage `json:"parsedText"` 380 ContentType protobuf.ChatMessage_ContentType `json:"contentType"` 381 AlbumID string `json:"albumId"` 382 ImageWidth uint32 `json:"imageWidth"` 383 ImageHeight uint32 `json:"imageHeight"` 384 AlbumImagesCount uint32 `json:"albumImagesCount"` 385 From string `json:"from"` 386 Deleted bool `json:"deleted,omitempty"` 387 DeletedForMe bool `json:"deletedForMe,omitempty"` 388 }{ 389 Alias: (*Alias)(m), 390 } 391 if err := json.Unmarshal(data, &aux); err != nil { 392 return err 393 } 394 if aux.ContentType == protobuf.ChatMessage_STICKER { 395 m.Payload = &protobuf.ChatMessage_Sticker{Sticker: aux.Sticker} 396 } 397 if aux.ContentType == protobuf.ChatMessage_AUDIO { 398 m.Payload = &protobuf.ChatMessage_Audio{ 399 Audio: &protobuf.AudioMessage{DurationMs: aux.AudioDurationMs}, 400 } 401 } 402 403 if aux.ContentType == protobuf.ChatMessage_IMAGE { 404 m.Payload = &protobuf.ChatMessage_Image{ 405 Image: &protobuf.ImageMessage{ 406 AlbumId: aux.AlbumID, 407 Width: aux.ImageWidth, 408 Height: aux.ImageHeight, 409 AlbumImagesCount: aux.AlbumImagesCount}, 410 } 411 } 412 413 m.ResponseTo = aux.ResponseTo 414 m.EnsName = aux.EnsName 415 m.DisplayName = aux.DisplayName 416 m.ChatId = aux.ChatID 417 m.ContentType = aux.ContentType 418 m.ParsedText = aux.ParsedText 419 m.From = aux.From 420 m.Deleted = aux.Deleted 421 m.DeletedForMe = aux.DeletedForMe 422 return nil 423 } 424 425 // Check if the first character is Hebrew or Arabic or the RTL character 426 func isRTL(s string) bool { 427 first, _ := utf8.DecodeRuneInString(s) 428 return unicode.Is(unicode.Hebrew, first) || 429 unicode.Is(unicode.Arabic, first) || 430 // RTL character 431 first == '\u200f' 432 } 433 434 // parseImage check the message contains an image, and if so 435 // it creates the a base64 encoded version of it. 436 func (m *Message) parseImage() error { 437 if m.ContentType != protobuf.ChatMessage_IMAGE { 438 return nil 439 } 440 image := m.GetImage() 441 if image == nil { 442 return errors.New("image empty") 443 } 444 445 payload := image.Payload 446 447 e64 := base64.StdEncoding 448 449 maxEncLen := e64.EncodedLen(len(payload)) 450 encBuf := make([]byte, maxEncLen) 451 452 e64.Encode(encBuf, payload) 453 454 mime, err := images.GetMimeType(image.Payload) 455 456 if err != nil { 457 return err 458 } 459 460 m.Base64Image = fmt.Sprintf("data:image/%s;base64,%s", mime, encBuf) 461 462 return nil 463 } 464 465 // parseAudio check the message contains an audio, and if so 466 // it creates a base64 encoded version of it. 467 func (m *Message) parseAudio() error { 468 if m.ContentType != protobuf.ChatMessage_AUDIO { 469 return nil 470 } 471 audio := m.GetAudio() 472 if audio == nil { 473 return errors.New("audio empty") 474 } 475 476 payload := audio.Payload 477 478 e64 := base64.StdEncoding 479 480 maxEncLen := e64.EncodedLen(len(payload)) 481 encBuf := make([]byte, maxEncLen) 482 483 e64.Encode(encBuf, payload) 484 485 mime, err := getAudioMessageMIME(audio) 486 487 if err != nil { 488 return err 489 } 490 491 m.Base64Audio = fmt.Sprintf("data:audio/%s;base64,%s", mime, encBuf) 492 493 return nil 494 } 495 496 // implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701 497 type SimplifiedTextVisitor struct { 498 text string 499 canonicalNames map[string]string 500 } 501 502 func (v *SimplifiedTextVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus { 503 // only on entering we fetch, otherwise we go on 504 if !entering { 505 return ast.GoToNext 506 } 507 508 switch n := node.(type) { 509 case *ast.Mention: 510 literal := string(n.Literal) 511 canonicalName, ok := v.canonicalNames[literal] 512 if ok { 513 v.text += canonicalName 514 } else { 515 v.text += literal 516 } 517 case *ast.Link: 518 destination := string(n.Destination) 519 v.text += destination 520 default: 521 var literal string 522 523 leaf := node.AsLeaf() 524 container := node.AsContainer() 525 if leaf != nil { 526 literal = string(leaf.Literal) 527 } else if container != nil { 528 literal = string(container.Literal) 529 } 530 v.text += literal 531 } 532 533 return ast.GoToNext 534 } 535 536 // implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701 537 type MentionsAndLinksVisitor struct { 538 identity string 539 mentioned bool 540 mentions []string 541 links []string 542 } 543 544 type LinksVisitor struct { 545 Links []string 546 } 547 548 func (v *MentionsAndLinksVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus { 549 // only on entering we fetch, otherwise we go on 550 if !entering { 551 return ast.GoToNext 552 } 553 switch n := node.(type) { 554 case *ast.Mention: 555 mention := string(n.Literal) 556 if mention == v.identity || mention == EveryoneMentionTag { 557 v.mentioned = true 558 } 559 v.mentions = append(v.mentions, mention) 560 case *ast.Link: 561 v.links = append(v.links, string(n.Destination)) 562 } 563 564 return ast.GoToNext 565 } 566 567 func (v *LinksVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus { 568 if !entering { 569 return ast.GoToNext 570 } 571 572 switch n := node.(type) { 573 case *ast.Link: 574 v.Links = append(v.Links, string(n.Destination)) 575 } 576 577 return ast.GoToNext 578 } 579 580 func runMentionsAndLinksVisitor(parsedText ast.Node, identity string) *MentionsAndLinksVisitor { 581 visitor := &MentionsAndLinksVisitor{identity: identity} 582 ast.Walk(parsedText, visitor) 583 return visitor 584 } 585 586 func RunLinksVisitor(parsedText ast.Node) *LinksVisitor { 587 visitor := &LinksVisitor{} 588 ast.Walk(parsedText, visitor) 589 return visitor 590 } 591 592 // PrepareContent return the parsed content of the message, the line-count and whether 593 // is a right-to-left message 594 func (m *Message) PrepareContent(identity string) error { 595 var parsedText ast.Node 596 switch m.ContentType { 597 case protobuf.ChatMessage_DISCORD_MESSAGE: 598 parsedText = markdown.Parse([]byte(m.GetDiscordMessage().Content), nil) 599 case protobuf.ChatMessage_BRIDGE_MESSAGE: 600 parsedText = markdown.Parse([]byte(m.GetBridgeMessage().Content), nil) 601 default: 602 parsedText = markdown.Parse([]byte(m.Text), nil) 603 } 604 605 visitor := runMentionsAndLinksVisitor(parsedText, identity) 606 m.Mentions = visitor.mentions 607 m.Links = visitor.links 608 // Leave it set if already set, as sometimes we might run this without 609 // an identity 610 if !m.Mentioned || identity != "" { 611 m.Mentioned = visitor.mentioned 612 } 613 jsonParsedText, err := json.Marshal(parsedText) 614 if err != nil { 615 return err 616 } 617 m.ParsedTextAst = &parsedText 618 m.ParsedText = jsonParsedText 619 m.LineCount = strings.Count(m.Text, "\n") 620 m.RTL = isRTL(m.Text) 621 if err := m.parseImage(); err != nil { 622 return err 623 } 624 return m.parseAudio() 625 } 626 627 // GetSimplifiedText returns a the text stripped of all the markdown and with mentions 628 // replaced by canonical names 629 func (m *Message) GetSimplifiedText(identity string, canonicalNames map[string]string) (string, error) { 630 631 if m.ContentType == protobuf.ChatMessage_AUDIO { 632 return "Audio", nil 633 } 634 if m.ContentType == protobuf.ChatMessage_STICKER { 635 return "Sticker", nil 636 } 637 if m.ContentType == protobuf.ChatMessage_IMAGE { 638 return "Image", nil 639 } 640 if m.ContentType == protobuf.ChatMessage_COMMUNITY { 641 return "Community", nil 642 } 643 if m.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP { 644 return "Group", nil 645 } 646 647 if m.ParsedTextAst == nil { 648 err := m.PrepareContent(identity) 649 if err != nil { 650 return "", err 651 } 652 } 653 visitor := &SimplifiedTextVisitor{canonicalNames: canonicalNames} 654 ast.Walk(*m.ParsedTextAst, visitor) 655 return visitor.text, nil 656 } 657 658 func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) { 659 switch i.Type { 660 case protobuf.AudioMessage_AAC: 661 return "aac", nil 662 case protobuf.AudioMessage_AMR: 663 return "amr", nil 664 } 665 666 return "", errors.New("audio format not supported") 667 } 668 669 // GetSigPubKey returns an ecdsa encoded public key 670 // this function is required to implement the ChatEntity interface 671 func (m *Message) GetSigPubKey() *ecdsa.PublicKey { 672 return m.SigPubKey 673 } 674 675 // GetProtoBuf returns the struct's embedded protobuf struct 676 // this function is required to implement the ChatEntity interface 677 func (m *Message) GetProtobuf() proto.Message { 678 return m.ChatMessage 679 } 680 681 // SetMessageType a setter for the MessageType field 682 // this function is required to implement the ChatEntity interface 683 func (m *Message) SetMessageType(messageType protobuf.MessageType) { 684 m.MessageType = messageType 685 } 686 687 // WrapGroupMessage indicates whether we should wrap this in membership information 688 func (m *Message) WrapGroupMessage() bool { 689 return true 690 } 691 692 // GetPublicKey attempts to return or recreate the *ecdsa.PublicKey of the Message sender. 693 // If the m.SigPubKey is set this will be returned 694 // If the m.From is present the string is decoded and unmarshalled into a *ecdsa.PublicKey, the m.SigPubKey is set and returned 695 // Else an error is thrown 696 // This function differs from GetSigPubKey() as this function may return an error 697 func (m *Message) GetSenderPubKey() (*ecdsa.PublicKey, error) { 698 // TODO requires tests 699 700 if m.SigPubKey != nil { 701 return m.SigPubKey, nil 702 } 703 704 if len(m.From) > 0 { 705 fromB, err := hex.DecodeString(m.From[2:]) 706 if err != nil { 707 return nil, err 708 } 709 710 senderPubKey, err := crypto.UnmarshalPubkey(fromB) 711 if err != nil { 712 return nil, err 713 } 714 715 m.SigPubKey = senderPubKey 716 return senderPubKey, nil 717 } 718 719 return nil, errors.New("no Message.SigPubKey or Message.From set unable to get public key") 720 } 721 722 func (m *Message) LoadAudio() error { 723 file, err := os.Open(m.AudioPath) 724 if err != nil { 725 return err 726 } 727 defer file.Close() 728 729 payload, err := ioutil.ReadAll(file) 730 if err != nil { 731 return err 732 733 } 734 audioMessage := m.GetAudio() 735 if audioMessage == nil { 736 return errors.New("no audio has been passed") 737 } 738 audioMessage.Payload = payload 739 audioMessage.Type = audio.Type(payload) 740 m.Payload = &protobuf.ChatMessage_Audio{Audio: audioMessage} 741 return os.Remove(m.AudioPath) 742 } 743 744 func (m *Message) LoadImage() error { 745 payload, err := images.OpenAndAdjustImage(images.CroppedImage{ImagePath: m.ImagePath}, false) 746 747 if err != nil { 748 return err 749 } 750 imageMessage := m.GetImage() 751 imageMessage.Payload = payload 752 imageMessage.Format = images.GetProtobufImageFormat(payload) 753 m.Payload = &protobuf.ChatMessage_Image{Image: imageMessage} 754 755 return nil 756 } 757 758 func (m *Message) SetAlbumIDAndImagesCount(albumID string, imagesCount uint32) error { 759 imageMessage := m.GetImage() 760 if imageMessage == nil { 761 return errors.New("Image is empty") 762 } 763 imageMessage.AlbumId = albumID 764 imageMessage.AlbumImagesCount = imagesCount 765 m.Payload = &protobuf.ChatMessage_Image{Image: imageMessage} 766 767 return nil 768 } 769 770 func NewMessage() *Message { 771 return &Message{ 772 ChatMessage: &protobuf.ChatMessage{}, 773 } 774 }