status-go/protocol/message.go

160 lines
5.3 KiB
Go

package protocol
import (
"crypto/ecdsa"
"encoding/json"
"strings"
"unicode"
"unicode/utf8"
"github.com/gomarkdown/markdown"
"github.com/status-im/status-go/protocol/protobuf"
)
// QuotedMessage contains the original text of the message replied to
type QuotedMessage struct {
// From is a public key of the author of the message.
From string `json:"from"`
Text string `json:"text"`
}
const (
OutgoingStatusSending = "sending"
OutgoingStatusSent = "sent"
)
// Message represents a message record in the database,
// more specifically in user_messages_legacy table.
type Message struct {
protobuf.ChatMessage
// ID calculated as keccak256(compressedAuthorPubKey, data) where data is unencrypted payload.
ID string `json:"id"`
// WhisperTimestamp is a timestamp of a Whisper envelope.
WhisperTimestamp uint64 `json:"whisperTimestamp"`
// From is a public key of the author of the message.
From string `json:"from"`
// Random 3 words name
Alias string `json:"alias"`
// Identicon of the author
Identicon string `json:"identicon"`
// The chat id to be stored locally
LocalChatID string `json:"localChatId"`
RetryCount int `json:"retryCount"`
Seen bool `json:"seen"`
OutgoingStatus string `json:"outgoingStatus,omitempty"`
QuotedMessage *QuotedMessage `json:"quotedMessage"`
// Computed fields
RTL bool `json:"rtl"`
ParsedText []byte `json:"parsedText"`
LineCount int `json:"lineCount"`
SigPubKey *ecdsa.PublicKey `json:"-"`
// RawPayload is the marshaled payload, used for resending the message
RawPayload []byte `json:"-"`
}
func (m *Message) MarshalJSON() ([]byte, error) {
type MessageAlias Message
item := struct {
ID string `json:"id"`
WhisperTimestamp uint64 `json:"whisperTimestamp"`
From string `json:"from"`
Alias string `json:"alias"`
Identicon string `json:"identicon"`
RetryCount int `json:"retryCount"`
Seen bool `json:"seen"`
OutgoingStatus string `json:"outgoingStatus,omitempty"`
QuotedMessage *QuotedMessage `json:"quotedMessage"`
RTL bool `json:"rtl"`
ParsedText json.RawMessage `json:"parsedText"`
LineCount int `json:"lineCount"`
Text string `json:"text"`
ChatId string `json:"chatId"`
LocalChatID string `json:"localChatId"`
Clock uint64 `json:"clock"`
ResponseTo string `json:"responseTo"`
EnsName string `json:"ensName"`
Sticker *protobuf.StickerMessage `json:"sticker"`
Timestamp uint64 `json:"timestamp"`
ContentType protobuf.ChatMessage_ContentType `json:"contentType"`
MessageType protobuf.ChatMessage_MessageType `json:"messageType"`
}{
ID: m.ID,
WhisperTimestamp: m.WhisperTimestamp,
From: m.From,
Alias: m.Alias,
Identicon: m.Identicon,
RetryCount: m.RetryCount,
Seen: m.Seen,
OutgoingStatus: m.OutgoingStatus,
QuotedMessage: m.QuotedMessage,
RTL: m.RTL,
ParsedText: m.ParsedText,
LineCount: m.LineCount,
Text: m.Text,
ChatId: m.ChatId,
LocalChatID: m.LocalChatID,
Clock: m.Clock,
ResponseTo: m.ResponseTo,
EnsName: m.EnsName,
Timestamp: m.Timestamp,
ContentType: m.ContentType,
MessageType: m.MessageType,
Sticker: m.GetSticker(),
}
return json.Marshal(item)
}
func (m *Message) UnmarshalJSON(data []byte) error {
type Alias Message
aux := struct {
*Alias
ResponseTo string `json:"responseTo"`
EnsName string `json:"ensName"`
ChatID string `json:"chatId"`
Sticker *protobuf.StickerMessage `json:"sticker"`
ContentType protobuf.ChatMessage_ContentType `json:"contentType"`
}{
Alias: (*Alias)(m),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
if aux.ContentType == protobuf.ChatMessage_STICKER {
m.Payload = &protobuf.ChatMessage_Sticker{Sticker: aux.Sticker}
}
m.ResponseTo = aux.ResponseTo
m.EnsName = aux.EnsName
m.ChatId = aux.ChatID
m.ContentType = aux.ContentType
return nil
}
// Check if the first character is Hebrew or Arabic or the RTL character
func isRTL(s string) bool {
first, _ := utf8.DecodeRuneInString(s)
return unicode.Is(unicode.Hebrew, first) ||
unicode.Is(unicode.Arabic, first) ||
// RTL character
first == '\u200f'
}
// PrepareContent return the parsed content of the message, the line-count and whether
// is a right-to-left message
func (m *Message) PrepareContent() error {
parsedText := markdown.Parse([]byte(m.Text), nil)
jsonParsedText, err := json.Marshal(parsedText)
if err != nil {
return err
}
m.ParsedText = jsonParsedText
m.LineCount = strings.Count(m.Text, "\n")
m.RTL = isRTL(m.Text)
return nil
}