2019-11-21 16:19:22 +00:00
|
|
|
package protocol
|
2019-07-17 22:25:42 +00:00
|
|
|
|
2019-08-29 06:33:46 +00:00
|
|
|
import (
|
|
|
|
"crypto/ecdsa"
|
2021-10-01 09:59:42 +00:00
|
|
|
"encoding/json"
|
2020-02-07 11:56:30 +00:00
|
|
|
"errors"
|
2019-12-02 15:34:05 +00:00
|
|
|
"math/rand"
|
2023-11-02 05:56:06 +00:00
|
|
|
"strings"
|
2023-04-16 15:06:00 +00:00
|
|
|
"time"
|
2019-08-29 06:33:46 +00:00
|
|
|
|
2023-08-03 14:16:11 +00:00
|
|
|
"github.com/status-im/status-go/deprecation"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2022-08-08 15:22:22 +00:00
|
|
|
userimage "github.com/status-im/status-go/images"
|
2020-09-01 13:27:01 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/communities"
|
2019-12-02 15:34:05 +00:00
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
2023-04-16 15:06:00 +00:00
|
|
|
"github.com/status-im/status-go/protocol/requests"
|
2019-11-21 16:19:22 +00:00
|
|
|
v1protocol "github.com/status-im/status-go/protocol/v1"
|
2023-11-02 05:56:06 +00:00
|
|
|
"github.com/status-im/status-go/services/utils"
|
2019-08-29 06:33:46 +00:00
|
|
|
)
|
2019-07-17 22:25:42 +00:00
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
var chatColors = []string{
|
|
|
|
"#fa6565", // red
|
|
|
|
"#887af9", // blue
|
|
|
|
"#FE8F59", // orange
|
|
|
|
"#7cda00", // green
|
|
|
|
"#51d0f0", // light-blue
|
|
|
|
"#d37ef4", // purple
|
|
|
|
}
|
|
|
|
|
2019-07-30 18:39:16 +00:00
|
|
|
type ChatType int
|
|
|
|
|
2024-03-06 17:25:19 +00:00
|
|
|
type ChatContext string
|
|
|
|
|
2019-07-30 18:39:16 +00:00
|
|
|
const (
|
2019-10-09 14:22:53 +00:00
|
|
|
ChatTypeOneToOne ChatType = iota + 1
|
2019-07-30 18:39:16 +00:00
|
|
|
ChatTypePublic
|
|
|
|
ChatTypePrivateGroupChat
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: CreateProfileChat shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2020-10-20 15:10:28 +00:00
|
|
|
ChatTypeProfile
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: ChatTypeTimeline shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2020-11-03 10:16:05 +00:00
|
|
|
ChatTypeTimeline
|
2020-11-18 09:16:51 +00:00
|
|
|
ChatTypeCommunityChat
|
2019-07-30 18:39:16 +00:00
|
|
|
)
|
|
|
|
|
2022-09-02 08:36:07 +00:00
|
|
|
const (
|
|
|
|
FirstMessageTimestampUndefined = 0
|
|
|
|
FirstMessageTimestampNoMessage = 1
|
|
|
|
)
|
|
|
|
|
2023-04-16 15:06:00 +00:00
|
|
|
const (
|
|
|
|
MuteFor1MinDuration = time.Minute
|
|
|
|
MuteFor15MinsDuration = 15 * time.Minute
|
|
|
|
MuteFor1HrsDuration = time.Hour
|
|
|
|
MuteFor8HrsDuration = 8 * time.Hour
|
2024-09-04 14:41:45 +00:00
|
|
|
MuteFor24HrsDuration = 24 * time.Hour
|
2023-04-16 15:06:00 +00:00
|
|
|
MuteFor1WeekDuration = 7 * 24 * time.Hour
|
|
|
|
)
|
|
|
|
|
2024-09-10 10:25:40 +00:00
|
|
|
// NOTE: Add items to the end of the list, because desktop and mobile
|
|
|
|
// use this enum by number rater than by string.
|
2023-04-16 15:06:00 +00:00
|
|
|
const (
|
|
|
|
MuteFor15Min requests.MutingVariation = iota + 1
|
|
|
|
MuteFor1Hr
|
|
|
|
MuteFor8Hr
|
|
|
|
MuteFor1Week
|
|
|
|
MuteTillUnmuted
|
|
|
|
MuteTill1Min
|
2023-06-02 11:25:32 +00:00
|
|
|
Unmuted
|
2024-09-10 10:25:40 +00:00
|
|
|
MuteFor24Hr
|
2023-04-16 15:06:00 +00:00
|
|
|
)
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
const pkStringLength = 68
|
|
|
|
|
2021-03-25 15:15:22 +00:00
|
|
|
// timelineChatID is a magic constant id for your own timeline
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: timeline chats are no more supported
|
2021-03-25 15:15:22 +00:00
|
|
|
const timelineChatID = "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a"
|
|
|
|
|
2019-07-30 18:39:16 +00:00
|
|
|
type Chat struct {
|
|
|
|
// ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one
|
|
|
|
// is the hex encoded public key and for group chats is a random uuid appended with
|
|
|
|
// the hex encoded pk of the creator of the chat
|
2021-06-01 12:13:17 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Color string `json:"color"`
|
2021-10-04 13:02:25 +00:00
|
|
|
Emoji string `json:"emoji"`
|
2019-07-30 18:39:16 +00:00
|
|
|
// Active indicates whether the chat has been soft deleted
|
|
|
|
Active bool `json:"active"`
|
|
|
|
|
2024-03-06 21:46:41 +00:00
|
|
|
// ViewersCanPostReactions indicates whether users can post reactions in view only mode
|
|
|
|
ViewersCanPostReactions bool `json:"viewersCanPostReactions"`
|
|
|
|
|
2019-07-30 18:39:16 +00:00
|
|
|
ChatType ChatType `json:"chatType"`
|
|
|
|
|
|
|
|
// Timestamp indicates the last time this chat has received/sent a message
|
|
|
|
Timestamp int64 `json:"timestamp"`
|
|
|
|
// LastClockValue indicates the last clock value to be used when sending messages
|
|
|
|
LastClockValue uint64 `json:"lastClockValue"`
|
|
|
|
// DeletedAtClockValue indicates the clock value at time of deletion, messages
|
|
|
|
// with lower clock value of this should be discarded
|
|
|
|
DeletedAtClockValue uint64 `json:"deletedAtClockValue"`
|
2021-10-12 10:33:32 +00:00
|
|
|
// ReadMessagesAtClockValue indicates the clock value of time till all
|
|
|
|
// messages are considered as read
|
|
|
|
ReadMessagesAtClockValue uint64
|
2019-07-30 18:39:16 +00:00
|
|
|
// Denormalized fields
|
2020-09-01 13:27:01 +00:00
|
|
|
UnviewedMessagesCount uint `json:"unviewedMessagesCount"`
|
2021-05-26 06:38:25 +00:00
|
|
|
UnviewedMentionsCount uint `json:"unviewedMentionsCount"`
|
2020-09-01 13:27:01 +00:00
|
|
|
LastMessage *common.Message `json:"lastMessage"`
|
2019-07-30 18:39:16 +00:00
|
|
|
|
|
|
|
// Group chat fields
|
|
|
|
// Members are the members who have been invited to the group chat
|
|
|
|
Members []ChatMember `json:"members"`
|
|
|
|
// MembershipUpdates is all the membership events in the chat
|
2019-12-02 15:34:05 +00:00
|
|
|
MembershipUpdates []v1protocol.MembershipUpdateEvent `json:"membershipUpdateEvents"`
|
2020-05-20 12:16:12 +00:00
|
|
|
|
|
|
|
// Generated username name of the chat for one-to-ones
|
|
|
|
Alias string `json:"alias,omitempty"`
|
|
|
|
// Identicon generated from public key
|
|
|
|
Identicon string `json:"identicon"`
|
2020-06-26 07:46:14 +00:00
|
|
|
|
|
|
|
// Muted is used to check whether we want to receive
|
|
|
|
// push notifications for this chat
|
2021-09-03 08:26:05 +00:00
|
|
|
Muted bool `json:"muted"`
|
2020-08-07 13:49:37 +00:00
|
|
|
|
2023-04-16 15:06:00 +00:00
|
|
|
// Time in which chat was muted
|
|
|
|
MuteTill time.Time `json:"muteTill,omitempty"`
|
|
|
|
|
2020-08-07 13:49:37 +00:00
|
|
|
// Public key of administrator who created invitation link
|
|
|
|
InvitationAdmin string `json:"invitationAdmin,omitempty"`
|
2020-10-20 15:10:28 +00:00
|
|
|
|
2021-11-25 15:21:42 +00:00
|
|
|
// Public key of administrator who sent us group invitation
|
|
|
|
ReceivedInvitationAdmin string `json:"receivedInvitationAdmin,omitempty"`
|
|
|
|
|
2020-10-20 15:10:28 +00:00
|
|
|
// Public key of user profile
|
|
|
|
Profile string `json:"profile,omitempty"`
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
// CommunityID is the id of the community it belongs to
|
|
|
|
CommunityID string `json:"communityId,omitempty"`
|
2021-05-23 13:34:17 +00:00
|
|
|
|
|
|
|
// CategoryID is the id of the community category this chat belongs to.
|
|
|
|
CategoryID string `json:"categoryId,omitempty"`
|
|
|
|
|
2021-05-18 08:48:42 +00:00
|
|
|
// Joined is a timestamp that indicates when the chat was joined
|
|
|
|
Joined int64 `json:"joined,omitempty"`
|
2021-03-25 15:15:22 +00:00
|
|
|
|
|
|
|
// SyncedTo is the time up until it has synced with a mailserver
|
|
|
|
SyncedTo uint32 `json:"syncedTo,omitempty"`
|
|
|
|
|
|
|
|
// SyncedFrom is the time from when it was synced with a mailserver
|
|
|
|
SyncedFrom uint32 `json:"syncedFrom,omitempty"`
|
2021-10-21 17:04:56 +00:00
|
|
|
|
2022-09-02 08:36:07 +00:00
|
|
|
// FirstMessageTimestamp is the time when first message was sent/received on the chat
|
|
|
|
// valid only for community chats
|
|
|
|
// 0 - undefined
|
|
|
|
// 1 - no messages
|
|
|
|
FirstMessageTimestamp uint32 `json:"firstMessageTimestamp,omitempty"`
|
|
|
|
|
2021-10-21 17:04:56 +00:00
|
|
|
// Highlight is used for highlight chats
|
|
|
|
Highlight bool `json:"highlight,omitempty"`
|
2022-08-08 15:22:22 +00:00
|
|
|
|
|
|
|
// Image of the chat in Base64 format
|
|
|
|
Base64Image string `json:"image,omitempty"`
|
2024-02-28 11:48:37 +00:00
|
|
|
|
|
|
|
// If true, the chat is invisible if permissions are not met
|
|
|
|
HideIfPermissionsNotMet bool `json:"hideIfPermissionsNotMet,omitempty"`
|
2019-12-02 15:34:05 +00:00
|
|
|
}
|
|
|
|
|
2021-09-07 14:05:36 +00:00
|
|
|
type ChatPreview struct {
|
|
|
|
// ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one
|
|
|
|
// is the hex encoded public key and for group chats is a random uuid appended with
|
|
|
|
// the hex encoded pk of the creator of the chat
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Color string `json:"color"`
|
2021-10-04 13:02:25 +00:00
|
|
|
Emoji string `json:"emoji"`
|
2021-09-07 14:05:36 +00:00
|
|
|
// Active indicates whether the chat has been soft deleted
|
|
|
|
Active bool `json:"active"`
|
|
|
|
|
|
|
|
ChatType ChatType `json:"chatType"`
|
|
|
|
|
|
|
|
// Timestamp indicates the last time this chat has received/sent a message
|
|
|
|
Timestamp int64 `json:"timestamp"`
|
|
|
|
// LastClockValue indicates the last clock value to be used when sending messages
|
|
|
|
LastClockValue uint64 `json:"lastClockValue"`
|
|
|
|
// DeletedAtClockValue indicates the clock value at time of deletion, messages
|
|
|
|
// with lower clock value of this should be discarded
|
|
|
|
DeletedAtClockValue uint64 `json:"deletedAtClockValue"`
|
|
|
|
|
|
|
|
// Denormalized fields
|
|
|
|
UnviewedMessagesCount uint `json:"unviewedMessagesCount"`
|
|
|
|
UnviewedMentionsCount uint `json:"unviewedMentionsCount"`
|
|
|
|
|
|
|
|
// Generated username name of the chat for one-to-ones
|
|
|
|
Alias string `json:"alias,omitempty"`
|
|
|
|
// Identicon generated from public key
|
|
|
|
Identicon string `json:"identicon"`
|
|
|
|
|
|
|
|
// Muted is used to check whether we want to receive
|
|
|
|
// push notifications for this chat
|
|
|
|
Muted bool `json:"muted,omitempty"`
|
|
|
|
|
2023-04-16 15:06:00 +00:00
|
|
|
// Time in which chat will be ummuted
|
|
|
|
MuteTill time.Time `json:"muteTill,omitempty"`
|
|
|
|
|
2021-09-07 14:05:36 +00:00
|
|
|
// Public key of user profile
|
|
|
|
Profile string `json:"profile,omitempty"`
|
|
|
|
|
|
|
|
// CommunityID is the id of the community it belongs to
|
|
|
|
CommunityID string `json:"communityId,omitempty"`
|
|
|
|
|
|
|
|
// CategoryID is the id of the community category this chat belongs to.
|
|
|
|
CategoryID string `json:"categoryId,omitempty"`
|
|
|
|
|
|
|
|
// Joined is a timestamp that indicates when the chat was joined
|
|
|
|
Joined int64 `json:"joined,omitempty"`
|
|
|
|
|
|
|
|
// SyncedTo is the time up until it has synced with a mailserver
|
|
|
|
SyncedTo uint32 `json:"syncedTo,omitempty"`
|
|
|
|
|
|
|
|
// SyncedFrom is the time from when it was synced with a mailserver
|
|
|
|
SyncedFrom uint32 `json:"syncedFrom,omitempty"`
|
|
|
|
|
2021-10-01 09:59:42 +00:00
|
|
|
// ParsedText is the parsed markdown for displaying
|
|
|
|
ParsedText json.RawMessage `json:"parsedText,omitempty"`
|
|
|
|
|
2021-09-07 14:05:36 +00:00
|
|
|
Text string `json:"text,omitempty"`
|
|
|
|
|
|
|
|
ContentType protobuf.ChatMessage_ContentType `json:"contentType,omitempty"`
|
2021-10-21 17:04:56 +00:00
|
|
|
|
|
|
|
// Highlight is used for highlight chats
|
|
|
|
Highlight bool `json:"highlight,omitempty"`
|
|
|
|
|
|
|
|
// Used for display invited community's name in the last message
|
|
|
|
ContentCommunityID string `json:"contentCommunityId,omitempty"`
|
2022-11-03 08:57:51 +00:00
|
|
|
|
|
|
|
// Members array to represent how many there are for chats preview of group chats
|
|
|
|
Members []ChatMember `json:"members"`
|
2023-05-08 17:02:54 +00:00
|
|
|
|
|
|
|
OutgoingStatus string `json:"outgoingStatus,omitempty"`
|
|
|
|
ResponseTo string `json:"responseTo"`
|
|
|
|
AlbumImagesCount uint32 `json:"albumImagesCount,omitempty"`
|
|
|
|
From string `json:"from"`
|
|
|
|
Deleted bool `json:"deleted"`
|
|
|
|
DeletedForMe bool `json:"deletedForMe"`
|
2024-04-09 15:39:14 +00:00
|
|
|
|
|
|
|
// Image of the chat in Base64 format
|
|
|
|
Base64Image string `json:"image,omitempty"`
|
2021-09-07 14:05:36 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 15:34:05 +00:00
|
|
|
func (c *Chat) PublicKey() (*ecdsa.PublicKey, error) {
|
|
|
|
// For one to one chatID is an encoded public key
|
|
|
|
if c.ChatType != ChatTypeOneToOne {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
return common.HexToPubkey(c.ID)
|
2019-07-30 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 07:25:09 +00:00
|
|
|
func (c *Chat) Public() bool {
|
|
|
|
return c.ChatType == ChatTypePublic
|
|
|
|
}
|
|
|
|
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: ProfileUpdates shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2020-10-20 15:10:28 +00:00
|
|
|
func (c *Chat) ProfileUpdates() bool {
|
2021-03-04 16:41:41 +00:00
|
|
|
return c.ChatType == ChatTypeProfile || len(c.Profile) > 0
|
2020-10-20 15:10:28 +00:00
|
|
|
}
|
|
|
|
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: Timeline shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2020-11-03 10:16:05 +00:00
|
|
|
func (c *Chat) Timeline() bool {
|
2021-03-04 13:02:08 +00:00
|
|
|
return c.ChatType == ChatTypeTimeline
|
2020-11-03 10:16:05 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 11:56:30 +00:00
|
|
|
func (c *Chat) OneToOne() bool {
|
|
|
|
return c.ChatType == ChatTypeOneToOne
|
|
|
|
}
|
|
|
|
|
2021-03-31 16:23:45 +00:00
|
|
|
func (c *Chat) CommunityChat() bool {
|
|
|
|
return c.ChatType == ChatTypeCommunityChat
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Chat) PrivateGroupChat() bool {
|
|
|
|
return c.ChatType == ChatTypePrivateGroupChat
|
|
|
|
}
|
|
|
|
|
2023-03-24 15:19:31 +00:00
|
|
|
func (c *Chat) IsActivePersonalChat() bool {
|
2023-03-28 08:50:28 +00:00
|
|
|
return c.Active && (c.OneToOne() || c.PrivateGroupChat() || c.Public()) && c.CommunityID == ""
|
2023-03-24 15:19:31 +00:00
|
|
|
}
|
|
|
|
|
2024-05-30 12:18:54 +00:00
|
|
|
// DefaultResendType returns the resend type for a chat.
|
|
|
|
// This function currently infers the ResendType from the chat type.
|
|
|
|
// Note that specific message might have different resent types. At times
|
|
|
|
// some messages dictate their ResendType based on their own properties and
|
|
|
|
// context, rather than the chat type it is associated with.
|
|
|
|
func (c *Chat) DefaultResendType() common.ResendType {
|
|
|
|
if c.OneToOne() || c.PrivateGroupChat() {
|
|
|
|
return common.ResendTypeDataSync
|
|
|
|
}
|
|
|
|
|
|
|
|
return common.ResendTypeRawMessage
|
|
|
|
}
|
|
|
|
|
2023-10-17 13:24:15 +00:00
|
|
|
func (c *Chat) shouldBeSynced() bool {
|
|
|
|
isPublicChat := !c.Timeline() && !c.ProfileUpdates() && c.Public()
|
|
|
|
return isPublicChat || c.OneToOne() || c.PrivateGroupChat()
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (c *Chat) CommunityChatID() string {
|
|
|
|
if c.ChatType != ChatTypeCommunityChat {
|
|
|
|
return c.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strips out the local prefix of the community-id
|
|
|
|
return c.ID[pkStringLength:]
|
|
|
|
}
|
|
|
|
|
2020-02-07 11:56:30 +00:00
|
|
|
func (c *Chat) Validate() error {
|
|
|
|
if c.ID == "" {
|
|
|
|
return errors.New("chatID can't be blank")
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.OneToOne() {
|
|
|
|
_, err := c.PublicKey()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
func (c *Chat) MembersAsPublicKeys() ([]*ecdsa.PublicKey, error) {
|
|
|
|
publicKeys := make([]string, len(c.Members))
|
|
|
|
for idx, item := range c.Members {
|
|
|
|
publicKeys[idx] = item.ID
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
return stringSliceToPublicKeys(publicKeys)
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
|
|
|
|
2020-04-22 12:58:28 +00:00
|
|
|
func (c *Chat) HasMember(memberID string) bool {
|
|
|
|
for _, member := range c.Members {
|
|
|
|
if memberID == member.ID {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-05-20 10:53:28 +00:00
|
|
|
func (c *Chat) RemoveMember(memberID string) {
|
|
|
|
members := c.Members
|
|
|
|
c.Members = []ChatMember{}
|
|
|
|
for _, member := range members {
|
|
|
|
if memberID != member.ID {
|
|
|
|
c.Members = append(c.Members, member)
|
2021-04-07 12:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Chat) updateChatFromGroupMembershipChanges(g *v1protocol.Group) {
|
2020-04-22 12:58:28 +00:00
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
// ID
|
|
|
|
c.ID = g.ChatID()
|
|
|
|
|
|
|
|
// Name
|
|
|
|
c.Name = g.Name()
|
|
|
|
|
2022-07-12 07:48:58 +00:00
|
|
|
// Color
|
2022-08-31 16:13:18 +00:00
|
|
|
color := g.Color()
|
|
|
|
if color != "" {
|
|
|
|
c.Color = g.Color()
|
|
|
|
}
|
2022-07-12 07:48:58 +00:00
|
|
|
|
2022-08-08 15:22:22 +00:00
|
|
|
// Image
|
|
|
|
base64Image, err := userimage.GetPayloadDataURI(g.Image())
|
|
|
|
if err == nil {
|
|
|
|
c.Base64Image = base64Image
|
|
|
|
}
|
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
// Members
|
|
|
|
members := g.Members()
|
|
|
|
admins := g.Admins()
|
|
|
|
chatMembers := make([]ChatMember, 0, len(members))
|
|
|
|
for _, m := range members {
|
2020-04-22 12:58:28 +00:00
|
|
|
|
2019-10-14 14:10:48 +00:00
|
|
|
chatMember := ChatMember{
|
|
|
|
ID: m,
|
|
|
|
}
|
|
|
|
chatMember.Admin = stringSliceContains(admins, m)
|
|
|
|
chatMembers = append(chatMembers, chatMember)
|
|
|
|
}
|
|
|
|
c.Members = chatMembers
|
|
|
|
|
|
|
|
// MembershipUpdates
|
2019-12-02 15:34:05 +00:00
|
|
|
c.MembershipUpdates = g.Events()
|
2019-10-14 14:10:48 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 11:30:26 +00:00
|
|
|
// NextClockAndTimestamp returns the next clock value
|
|
|
|
// and the current timestamp
|
2020-11-18 09:16:51 +00:00
|
|
|
func (c *Chat) NextClockAndTimestamp(timesource common.TimeSource) (uint64, uint64) {
|
2020-02-07 11:30:26 +00:00
|
|
|
clock := c.LastClockValue
|
|
|
|
timestamp := timesource.GetCurrentTime()
|
|
|
|
if clock == 0 || clock < timestamp {
|
|
|
|
clock = timestamp
|
|
|
|
} else {
|
|
|
|
clock = clock + 1
|
|
|
|
}
|
2023-02-02 18:12:20 +00:00
|
|
|
c.LastClockValue = clock
|
|
|
|
|
2020-02-07 11:30:26 +00:00
|
|
|
return clock, timestamp
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (c *Chat) UpdateFromMessage(message *common.Message, timesource common.TimeSource) error {
|
2020-02-07 11:30:26 +00:00
|
|
|
c.Timestamp = int64(timesource.GetCurrentTime())
|
2020-07-30 20:54:33 +00:00
|
|
|
|
|
|
|
// If the clock of the last message is lower, we set the message
|
|
|
|
if c.LastMessage == nil || c.LastMessage.Clock <= message.Clock {
|
|
|
|
c.LastMessage = message
|
2020-02-07 11:30:26 +00:00
|
|
|
}
|
2020-07-30 19:14:47 +00:00
|
|
|
// If the clock is higher we set the clock
|
2020-07-30 20:54:33 +00:00
|
|
|
if c.LastClockValue < message.Clock {
|
2020-07-30 19:14:47 +00:00
|
|
|
c.LastClockValue = message.Clock
|
|
|
|
}
|
2020-02-07 11:30:26 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-02 08:36:07 +00:00
|
|
|
func (c *Chat) UpdateFirstMessageTimestamp(timestamp uint32) bool {
|
|
|
|
if timestamp == c.FirstMessageTimestamp {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not allow to assign `Undefined`` or `NoMessage` to already set timestamp
|
|
|
|
if timestamp == FirstMessageTimestampUndefined ||
|
|
|
|
(timestamp == FirstMessageTimestampNoMessage &&
|
|
|
|
c.FirstMessageTimestamp != FirstMessageTimestampUndefined) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.FirstMessageTimestamp == FirstMessageTimestampUndefined ||
|
|
|
|
c.FirstMessageTimestamp == FirstMessageTimestampNoMessage ||
|
|
|
|
timestamp < c.FirstMessageTimestamp {
|
|
|
|
c.FirstMessageTimestamp = timestamp
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-07-30 18:39:16 +00:00
|
|
|
// ChatMembershipUpdate represent an event on membership of the chat
|
|
|
|
type ChatMembershipUpdate struct {
|
|
|
|
// Unique identifier for the event
|
|
|
|
ID string `json:"id"`
|
2019-12-02 15:34:05 +00:00
|
|
|
// Type indicates the kind of event
|
|
|
|
Type protobuf.MembershipUpdateEvent_EventType `json:"type"`
|
2019-07-30 18:39:16 +00:00
|
|
|
// Name represents the name in the event of changing name events
|
2019-08-20 11:20:25 +00:00
|
|
|
Name string `json:"name,omitempty"`
|
2019-07-30 18:39:16 +00:00
|
|
|
// Clock value of the event
|
|
|
|
ClockValue uint64 `json:"clockValue"`
|
|
|
|
// Signature of the event
|
|
|
|
Signature string `json:"signature"`
|
|
|
|
// Hex encoded public key of the creator of the event
|
|
|
|
From string `json:"from"`
|
|
|
|
// Target of the event for single-target events
|
2019-08-20 11:20:25 +00:00
|
|
|
Member string `json:"member,omitempty"`
|
2019-07-30 18:39:16 +00:00
|
|
|
// Target of the event for multi-target events
|
2019-08-20 11:20:25 +00:00
|
|
|
Members []string `json:"members,omitempty"`
|
2019-07-30 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ChatMember represents a member who participates in a group chat
|
|
|
|
type ChatMember struct {
|
|
|
|
// ID is the hex encoded public key of the member
|
|
|
|
ID string `json:"id"`
|
|
|
|
// Admin indicates if the member is an admin of the group chat
|
|
|
|
Admin bool `json:"admin"`
|
2019-07-17 22:25:42 +00:00
|
|
|
}
|
2019-08-29 06:33:46 +00:00
|
|
|
|
|
|
|
func (c ChatMember) PublicKey() (*ecdsa.PublicKey, error) {
|
2020-11-18 09:16:51 +00:00
|
|
|
return common.HexToPubkey(c.ID)
|
2019-08-29 06:33:46 +00:00
|
|
|
}
|
2019-09-26 07:01:17 +00:00
|
|
|
|
|
|
|
func oneToOneChatID(publicKey *ecdsa.PublicKey) string {
|
2019-11-23 17:57:05 +00:00
|
|
|
return types.EncodeHex(crypto.FromECDSAPub(publicKey))
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func OneToOneFromPublicKey(pk *ecdsa.PublicKey, timesource common.TimeSource) *Chat {
|
2020-01-10 18:59:01 +00:00
|
|
|
chatID := types.EncodeHex(crypto.FromECDSAPub(pk))
|
2020-01-20 16:44:32 +00:00
|
|
|
newChat := CreateOneToOneChat(chatID[:8], pk, timesource)
|
2020-01-10 18:59:01 +00:00
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
return newChat
|
2020-01-10 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey, timesource common.TimeSource) *Chat {
|
2021-10-12 10:33:32 +00:00
|
|
|
timestamp := timesource.GetCurrentTime()
|
2021-01-11 10:32:51 +00:00
|
|
|
return &Chat{
|
2021-10-12 10:33:32 +00:00
|
|
|
ID: oneToOneChatID(publicKey),
|
|
|
|
Name: name,
|
|
|
|
Timestamp: int64(timestamp),
|
|
|
|
ReadMessagesAtClockValue: 0,
|
|
|
|
Active: true,
|
|
|
|
Joined: int64(timestamp),
|
|
|
|
ChatType: ChatTypeOneToOne,
|
|
|
|
Highlight: true,
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat, timesource common.TimeSource) *Chat {
|
2020-11-18 09:16:51 +00:00
|
|
|
color := orgChat.Identity.Color
|
|
|
|
if color == "" {
|
2020-12-28 09:09:45 +00:00
|
|
|
color = chatColors[rand.Intn(len(chatColors))] // nolint: gosec
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2021-10-12 10:33:32 +00:00
|
|
|
timestamp := timesource.GetCurrentTime()
|
2024-05-22 19:43:06 +00:00
|
|
|
|
|
|
|
// Populate community _channel_ members to _chat_ members
|
|
|
|
chatMembers := []ChatMember{}
|
|
|
|
for pubKey := range orgChat.Members {
|
|
|
|
chatMember := ChatMember{
|
|
|
|
ID: pubKey,
|
|
|
|
Admin: false,
|
|
|
|
}
|
|
|
|
chatMembers = append(chatMembers, chatMember)
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
return &Chat{
|
2021-10-12 10:33:32 +00:00
|
|
|
CommunityID: orgID,
|
|
|
|
CategoryID: orgChat.CategoryId,
|
2024-02-28 11:48:37 +00:00
|
|
|
HideIfPermissionsNotMet: orgChat.HideIfPermissionsNotMet,
|
2021-10-12 10:33:32 +00:00
|
|
|
Name: orgChat.Identity.DisplayName,
|
|
|
|
Description: orgChat.Identity.Description,
|
2024-05-22 19:43:06 +00:00
|
|
|
Members: chatMembers,
|
2021-10-12 10:33:32 +00:00
|
|
|
Active: true,
|
|
|
|
Color: color,
|
|
|
|
Emoji: orgChat.Identity.Emoji,
|
|
|
|
ID: orgID + chatID,
|
|
|
|
Timestamp: int64(timestamp),
|
|
|
|
Joined: int64(timestamp),
|
|
|
|
ReadMessagesAtClockValue: 0,
|
|
|
|
ChatType: ChatTypeCommunityChat,
|
2022-09-02 08:36:07 +00:00
|
|
|
FirstMessageTimestamp: orgChat.Identity.FirstMessageTimestamp,
|
2024-03-06 21:46:41 +00:00
|
|
|
ViewersCanPostReactions: orgChat.ViewersCanPostReactions,
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-16 16:41:04 +00:00
|
|
|
func (c *Chat) CommunityChannelID() string {
|
|
|
|
return strings.TrimPrefix(c.ID, c.CommunityID)
|
|
|
|
}
|
|
|
|
|
2021-03-31 16:23:45 +00:00
|
|
|
func (c *Chat) DeepLink() string {
|
|
|
|
if c.OneToOne() {
|
2023-11-02 05:56:06 +00:00
|
|
|
return "status-app://p/" + c.ID
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
if c.PrivateGroupChat() {
|
2023-11-02 05:56:06 +00:00
|
|
|
return "status-app://g/args?a2=" + c.ID
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.CommunityChat() {
|
2024-05-16 16:41:04 +00:00
|
|
|
communityChannelID := c.CommunityChannelID()
|
2023-11-02 05:56:06 +00:00
|
|
|
pubkey, err := types.DecodeHex(c.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
serializedCommunityID, err := utils.SerializePublicKey(pubkey)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return "status-app://cc/" + communityChannelID + "#" + serializedCommunityID
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.Public() {
|
2023-11-02 05:56:06 +00:00
|
|
|
return "status-app://" + c.ID
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat {
|
|
|
|
var chats []*Chat
|
2020-11-18 09:16:51 +00:00
|
|
|
orgID := org.IDString()
|
|
|
|
|
|
|
|
for chatID, chat := range org.Chats() {
|
|
|
|
chats = append(chats, CreateCommunityChat(orgID, chatID, chat, timesource))
|
|
|
|
}
|
|
|
|
return chats
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func CreatePublicChat(name string, timesource common.TimeSource) *Chat {
|
2021-10-12 10:33:32 +00:00
|
|
|
timestamp := timesource.GetCurrentTime()
|
2021-01-11 10:32:51 +00:00
|
|
|
return &Chat{
|
2021-10-12 10:33:32 +00:00
|
|
|
ID: name,
|
|
|
|
Name: name,
|
|
|
|
Active: true,
|
|
|
|
Timestamp: int64(timestamp),
|
|
|
|
Joined: int64(timestamp),
|
|
|
|
ReadMessagesAtClockValue: 0,
|
|
|
|
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
|
|
|
ChatType: ChatTypePublic,
|
2024-05-22 19:43:06 +00:00
|
|
|
Members: []ChatMember{},
|
2019-09-26 07:01:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: buildProfileChatID shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2020-12-22 10:49:25 +00:00
|
|
|
func buildProfileChatID(publicKeyString string) string {
|
|
|
|
return "@" + publicKeyString
|
|
|
|
}
|
|
|
|
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: CreateProfileChat shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2021-03-25 15:15:22 +00:00
|
|
|
func CreateProfileChat(pubkey string, timesource common.TimeSource) *Chat {
|
2023-08-03 14:16:11 +00:00
|
|
|
// Return nil to prevent usage of deprecated function
|
|
|
|
if deprecation.ChatProfileDeprecated {
|
|
|
|
return nil
|
|
|
|
}
|
2021-03-25 15:15:22 +00:00
|
|
|
|
|
|
|
id := buildProfileChatID(pubkey)
|
2021-01-11 10:32:51 +00:00
|
|
|
return &Chat{
|
2020-12-22 10:49:25 +00:00
|
|
|
ID: id,
|
|
|
|
Name: id,
|
2020-10-20 15:10:28 +00:00
|
|
|
Active: true,
|
|
|
|
Timestamp: int64(timesource.GetCurrentTime()),
|
2021-05-14 10:55:42 +00:00
|
|
|
Joined: int64(timesource.GetCurrentTime()),
|
2020-12-28 09:09:45 +00:00
|
|
|
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
2020-10-20 15:10:28 +00:00
|
|
|
ChatType: ChatTypeProfile,
|
2021-03-25 15:15:22 +00:00
|
|
|
Profile: pubkey,
|
2020-10-20 15:10:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func CreateGroupChat(timesource common.TimeSource) Chat {
|
2021-05-24 08:27:56 +00:00
|
|
|
timestamp := timesource.GetCurrentTime()
|
|
|
|
synced := uint32(timestamp / 1000)
|
|
|
|
|
2019-10-09 14:22:53 +00:00
|
|
|
return Chat{
|
2021-10-12 10:33:32 +00:00
|
|
|
Active: true,
|
|
|
|
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
|
|
|
Timestamp: int64(timestamp),
|
|
|
|
ReadMessagesAtClockValue: 0,
|
|
|
|
SyncedTo: synced,
|
|
|
|
SyncedFrom: synced,
|
|
|
|
ChatType: ChatTypePrivateGroupChat,
|
|
|
|
Highlight: true,
|
2019-10-09 14:22:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-03 14:16:11 +00:00
|
|
|
// Deprecated: CreateTimelineChat shouldn't be used
|
|
|
|
// and is only left here in case profile chat feature is re-introduced.
|
2021-03-25 15:15:22 +00:00
|
|
|
func CreateTimelineChat(timesource common.TimeSource) *Chat {
|
2023-08-03 14:16:11 +00:00
|
|
|
// Return nil to prevent usage of deprecated function
|
|
|
|
if deprecation.ChatTimelineDeprecated {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-03-25 15:15:22 +00:00
|
|
|
return &Chat{
|
|
|
|
ID: timelineChatID,
|
|
|
|
Name: "#" + timelineChatID,
|
|
|
|
Timestamp: int64(timesource.GetCurrentTime()),
|
|
|
|
Active: true,
|
|
|
|
ChatType: ChatTypeTimeline,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func stringSliceToPublicKeys(slice []string) ([]*ecdsa.PublicKey, error) {
|
2019-10-14 14:10:48 +00:00
|
|
|
result := make([]*ecdsa.PublicKey, len(slice))
|
|
|
|
for idx, item := range slice {
|
2020-11-18 09:16:51 +00:00
|
|
|
var err error
|
|
|
|
result[idx], err = common.HexToPubkey(item)
|
2019-10-14 14:10:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringSliceContains(slice []string, item string) bool {
|
|
|
|
for _, s := range slice {
|
|
|
|
if s == item {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2024-03-06 17:25:19 +00:00
|
|
|
|
|
|
|
func GetChatContextFromChatType(chatType ChatType) ChatContext {
|
|
|
|
switch chatType {
|
|
|
|
case ChatTypeOneToOne, ChatTypePrivateGroupChat:
|
|
|
|
return privateChat
|
|
|
|
default:
|
|
|
|
return publicChat
|
|
|
|
}
|
|
|
|
}
|