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  }