github.com/status-im/status-go@v1.1.0/protocol/chat.go (about)

     1  package protocol
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"encoding/json"
     6  	"errors"
     7  	"math/rand"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/status-im/status-go/deprecation"
    12  	"github.com/status-im/status-go/eth-node/crypto"
    13  	"github.com/status-im/status-go/eth-node/types"
    14  	userimage "github.com/status-im/status-go/images"
    15  	"github.com/status-im/status-go/protocol/common"
    16  	"github.com/status-im/status-go/protocol/communities"
    17  	"github.com/status-im/status-go/protocol/protobuf"
    18  	"github.com/status-im/status-go/protocol/requests"
    19  	v1protocol "github.com/status-im/status-go/protocol/v1"
    20  	"github.com/status-im/status-go/services/utils"
    21  )
    22  
    23  var chatColors = []string{
    24  	"#fa6565", // red
    25  	"#887af9", // blue
    26  	"#FE8F59", // orange
    27  	"#7cda00", // green
    28  	"#51d0f0", // light-blue
    29  	"#d37ef4", // purple
    30  }
    31  
    32  type ChatType int
    33  
    34  type ChatContext string
    35  
    36  const (
    37  	ChatTypeOneToOne ChatType = iota + 1
    38  	ChatTypePublic
    39  	ChatTypePrivateGroupChat
    40  	// Deprecated: CreateProfileChat shouldn't be used
    41  	// and is only left here in case profile chat feature is re-introduced.
    42  	ChatTypeProfile
    43  	// Deprecated: ChatTypeTimeline shouldn't be used
    44  	// and is only left here in case profile chat feature is re-introduced.
    45  	ChatTypeTimeline
    46  	ChatTypeCommunityChat
    47  )
    48  
    49  const (
    50  	FirstMessageTimestampUndefined = 0
    51  	FirstMessageTimestampNoMessage = 1
    52  )
    53  
    54  const (
    55  	MuteFor1MinDuration   = time.Minute
    56  	MuteFor15MinsDuration = 15 * time.Minute
    57  	MuteFor1HrsDuration   = time.Hour
    58  	MuteFor8HrsDuration   = 8 * time.Hour
    59  	MuteFor24HrsDuration  = 24 * time.Hour
    60  	MuteFor1WeekDuration  = 7 * 24 * time.Hour
    61  )
    62  
    63  // NOTE: Add items to the end of the list, because desktop and mobile
    64  // use this enum by number rater than by string.
    65  const (
    66  	MuteFor15Min requests.MutingVariation = iota + 1
    67  	MuteFor1Hr
    68  	MuteFor8Hr
    69  	MuteFor1Week
    70  	MuteTillUnmuted
    71  	MuteTill1Min
    72  	Unmuted
    73  	MuteFor24Hr
    74  )
    75  
    76  const pkStringLength = 68
    77  
    78  // timelineChatID is a magic constant id for your own timeline
    79  // Deprecated: timeline chats are no more supported
    80  const timelineChatID = "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a"
    81  
    82  type Chat struct {
    83  	// ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one
    84  	// is the hex encoded public key and for group chats is a random uuid appended with
    85  	// the hex encoded pk of the creator of the chat
    86  	ID          string `json:"id"`
    87  	Name        string `json:"name"`
    88  	Description string `json:"description"`
    89  	Color       string `json:"color"`
    90  	Emoji       string `json:"emoji"`
    91  	// Active indicates whether the chat has been soft deleted
    92  	Active bool `json:"active"`
    93  
    94  	// ViewersCanPostReactions indicates whether users can post reactions in view only mode
    95  	ViewersCanPostReactions bool `json:"viewersCanPostReactions"`
    96  
    97  	ChatType ChatType `json:"chatType"`
    98  
    99  	// Timestamp indicates the last time this chat has received/sent a message
   100  	Timestamp int64 `json:"timestamp"`
   101  	// LastClockValue indicates the last clock value to be used when sending messages
   102  	LastClockValue uint64 `json:"lastClockValue"`
   103  	// DeletedAtClockValue indicates the clock value at time of deletion, messages
   104  	// with lower clock value of this should be discarded
   105  	DeletedAtClockValue uint64 `json:"deletedAtClockValue"`
   106  	// ReadMessagesAtClockValue indicates the clock value of time till all
   107  	// messages are considered as read
   108  	ReadMessagesAtClockValue uint64
   109  	// Denormalized fields
   110  	UnviewedMessagesCount uint            `json:"unviewedMessagesCount"`
   111  	UnviewedMentionsCount uint            `json:"unviewedMentionsCount"`
   112  	LastMessage           *common.Message `json:"lastMessage"`
   113  
   114  	// Group chat fields
   115  	// Members are the members who have been invited to the group chat
   116  	Members []ChatMember `json:"members"`
   117  	// MembershipUpdates is all the membership events in the chat
   118  	MembershipUpdates []v1protocol.MembershipUpdateEvent `json:"membershipUpdateEvents"`
   119  
   120  	// Generated username name of the chat for one-to-ones
   121  	Alias string `json:"alias,omitempty"`
   122  	// Identicon generated from public key
   123  	Identicon string `json:"identicon"`
   124  
   125  	// Muted is used to check whether we want to receive
   126  	// push notifications for this chat
   127  	Muted bool `json:"muted"`
   128  
   129  	// Time in which chat was muted
   130  	MuteTill time.Time `json:"muteTill,omitempty"`
   131  
   132  	// Public key of administrator who created invitation link
   133  	InvitationAdmin string `json:"invitationAdmin,omitempty"`
   134  
   135  	// Public key of administrator who sent us group invitation
   136  	ReceivedInvitationAdmin string `json:"receivedInvitationAdmin,omitempty"`
   137  
   138  	// Public key of user profile
   139  	Profile string `json:"profile,omitempty"`
   140  
   141  	// CommunityID is the id of the community it belongs to
   142  	CommunityID string `json:"communityId,omitempty"`
   143  
   144  	// CategoryID is the id of the community category this chat belongs to.
   145  	CategoryID string `json:"categoryId,omitempty"`
   146  
   147  	// Joined is a timestamp that indicates when the chat was joined
   148  	Joined int64 `json:"joined,omitempty"`
   149  
   150  	// SyncedTo is the time up until it has synced with a mailserver
   151  	SyncedTo uint32 `json:"syncedTo,omitempty"`
   152  
   153  	// SyncedFrom is the time from when it was synced with a mailserver
   154  	SyncedFrom uint32 `json:"syncedFrom,omitempty"`
   155  
   156  	// FirstMessageTimestamp is the time when first message was sent/received on the chat
   157  	// valid only for community chats
   158  	// 0 - undefined
   159  	// 1 - no messages
   160  	FirstMessageTimestamp uint32 `json:"firstMessageTimestamp,omitempty"`
   161  
   162  	// Highlight is used for highlight chats
   163  	Highlight bool `json:"highlight,omitempty"`
   164  
   165  	// Image of the chat in Base64 format
   166  	Base64Image string `json:"image,omitempty"`
   167  
   168  	// If true, the chat is invisible if permissions are not met
   169  	HideIfPermissionsNotMet bool `json:"hideIfPermissionsNotMet,omitempty"`
   170  }
   171  
   172  type ChatPreview struct {
   173  	// ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one
   174  	// is the hex encoded public key and for group chats is a random uuid appended with
   175  	// the hex encoded pk of the creator of the chat
   176  	ID          string `json:"id"`
   177  	Name        string `json:"name"`
   178  	Description string `json:"description"`
   179  	Color       string `json:"color"`
   180  	Emoji       string `json:"emoji"`
   181  	// Active indicates whether the chat has been soft deleted
   182  	Active bool `json:"active"`
   183  
   184  	ChatType ChatType `json:"chatType"`
   185  
   186  	// Timestamp indicates the last time this chat has received/sent a message
   187  	Timestamp int64 `json:"timestamp"`
   188  	// LastClockValue indicates the last clock value to be used when sending messages
   189  	LastClockValue uint64 `json:"lastClockValue"`
   190  	// DeletedAtClockValue indicates the clock value at time of deletion, messages
   191  	// with lower clock value of this should be discarded
   192  	DeletedAtClockValue uint64 `json:"deletedAtClockValue"`
   193  
   194  	// Denormalized fields
   195  	UnviewedMessagesCount uint `json:"unviewedMessagesCount"`
   196  	UnviewedMentionsCount uint `json:"unviewedMentionsCount"`
   197  
   198  	// Generated username name of the chat for one-to-ones
   199  	Alias string `json:"alias,omitempty"`
   200  	// Identicon generated from public key
   201  	Identicon string `json:"identicon"`
   202  
   203  	// Muted is used to check whether we want to receive
   204  	// push notifications for this chat
   205  	Muted bool `json:"muted,omitempty"`
   206  
   207  	// Time in which chat will be  ummuted
   208  	MuteTill time.Time `json:"muteTill,omitempty"`
   209  
   210  	// Public key of user profile
   211  	Profile string `json:"profile,omitempty"`
   212  
   213  	// CommunityID is the id of the community it belongs to
   214  	CommunityID string `json:"communityId,omitempty"`
   215  
   216  	// CategoryID is the id of the community category this chat belongs to.
   217  	CategoryID string `json:"categoryId,omitempty"`
   218  
   219  	// Joined is a timestamp that indicates when the chat was joined
   220  	Joined int64 `json:"joined,omitempty"`
   221  
   222  	// SyncedTo is the time up until it has synced with a mailserver
   223  	SyncedTo uint32 `json:"syncedTo,omitempty"`
   224  
   225  	// SyncedFrom is the time from when it was synced with a mailserver
   226  	SyncedFrom uint32 `json:"syncedFrom,omitempty"`
   227  
   228  	// ParsedText is the parsed markdown for displaying
   229  	ParsedText json.RawMessage `json:"parsedText,omitempty"`
   230  
   231  	Text string `json:"text,omitempty"`
   232  
   233  	ContentType protobuf.ChatMessage_ContentType `json:"contentType,omitempty"`
   234  
   235  	// Highlight is used for highlight chats
   236  	Highlight bool `json:"highlight,omitempty"`
   237  
   238  	// Used for display invited community's name in the last message
   239  	ContentCommunityID string `json:"contentCommunityId,omitempty"`
   240  
   241  	// Members array to represent how many there are for chats preview of group chats
   242  	Members []ChatMember `json:"members"`
   243  
   244  	OutgoingStatus   string `json:"outgoingStatus,omitempty"`
   245  	ResponseTo       string `json:"responseTo"`
   246  	AlbumImagesCount uint32 `json:"albumImagesCount,omitempty"`
   247  	From             string `json:"from"`
   248  	Deleted          bool   `json:"deleted"`
   249  	DeletedForMe     bool   `json:"deletedForMe"`
   250  
   251  	// Image of the chat in Base64 format
   252  	Base64Image string `json:"image,omitempty"`
   253  }
   254  
   255  func (c *Chat) PublicKey() (*ecdsa.PublicKey, error) {
   256  	// For one to one chatID is an encoded public key
   257  	if c.ChatType != ChatTypeOneToOne {
   258  		return nil, nil
   259  	}
   260  	return common.HexToPubkey(c.ID)
   261  }
   262  
   263  func (c *Chat) Public() bool {
   264  	return c.ChatType == ChatTypePublic
   265  }
   266  
   267  // Deprecated: ProfileUpdates shouldn't be used
   268  // and is only left here in case profile chat feature is re-introduced.
   269  func (c *Chat) ProfileUpdates() bool {
   270  	return c.ChatType == ChatTypeProfile || len(c.Profile) > 0
   271  }
   272  
   273  // Deprecated: Timeline shouldn't be used
   274  // and is only left here in case profile chat feature is re-introduced.
   275  func (c *Chat) Timeline() bool {
   276  	return c.ChatType == ChatTypeTimeline
   277  }
   278  
   279  func (c *Chat) OneToOne() bool {
   280  	return c.ChatType == ChatTypeOneToOne
   281  }
   282  
   283  func (c *Chat) CommunityChat() bool {
   284  	return c.ChatType == ChatTypeCommunityChat
   285  }
   286  
   287  func (c *Chat) PrivateGroupChat() bool {
   288  	return c.ChatType == ChatTypePrivateGroupChat
   289  }
   290  
   291  func (c *Chat) IsActivePersonalChat() bool {
   292  	return c.Active && (c.OneToOne() || c.PrivateGroupChat() || c.Public()) && c.CommunityID == ""
   293  }
   294  
   295  // DefaultResendType returns the resend type for a chat.
   296  // This function currently infers the ResendType from the chat type.
   297  // Note that specific message might have different resent types. At times
   298  // some messages dictate their ResendType based on their own properties and
   299  // context, rather than the chat type it is associated with.
   300  func (c *Chat) DefaultResendType() common.ResendType {
   301  	if c.OneToOne() || c.PrivateGroupChat() {
   302  		return common.ResendTypeDataSync
   303  	}
   304  
   305  	return common.ResendTypeRawMessage
   306  }
   307  
   308  func (c *Chat) shouldBeSynced() bool {
   309  	isPublicChat := !c.Timeline() && !c.ProfileUpdates() && c.Public()
   310  	return isPublicChat || c.OneToOne() || c.PrivateGroupChat()
   311  }
   312  
   313  func (c *Chat) CommunityChatID() string {
   314  	if c.ChatType != ChatTypeCommunityChat {
   315  		return c.ID
   316  	}
   317  
   318  	// Strips out the local prefix of the community-id
   319  	return c.ID[pkStringLength:]
   320  }
   321  
   322  func (c *Chat) Validate() error {
   323  	if c.ID == "" {
   324  		return errors.New("chatID can't be blank")
   325  	}
   326  
   327  	if c.OneToOne() {
   328  		_, err := c.PublicKey()
   329  		return err
   330  	}
   331  	return nil
   332  }
   333  
   334  func (c *Chat) MembersAsPublicKeys() ([]*ecdsa.PublicKey, error) {
   335  	publicKeys := make([]string, len(c.Members))
   336  	for idx, item := range c.Members {
   337  		publicKeys[idx] = item.ID
   338  	}
   339  	return stringSliceToPublicKeys(publicKeys)
   340  }
   341  
   342  func (c *Chat) HasMember(memberID string) bool {
   343  	for _, member := range c.Members {
   344  		if memberID == member.ID {
   345  			return true
   346  		}
   347  	}
   348  
   349  	return false
   350  }
   351  
   352  func (c *Chat) RemoveMember(memberID string) {
   353  	members := c.Members
   354  	c.Members = []ChatMember{}
   355  	for _, member := range members {
   356  		if memberID != member.ID {
   357  			c.Members = append(c.Members, member)
   358  		}
   359  	}
   360  }
   361  
   362  func (c *Chat) updateChatFromGroupMembershipChanges(g *v1protocol.Group) {
   363  
   364  	// ID
   365  	c.ID = g.ChatID()
   366  
   367  	// Name
   368  	c.Name = g.Name()
   369  
   370  	// Color
   371  	color := g.Color()
   372  	if color != "" {
   373  		c.Color = g.Color()
   374  	}
   375  
   376  	// Image
   377  	base64Image, err := userimage.GetPayloadDataURI(g.Image())
   378  	if err == nil {
   379  		c.Base64Image = base64Image
   380  	}
   381  
   382  	// Members
   383  	members := g.Members()
   384  	admins := g.Admins()
   385  	chatMembers := make([]ChatMember, 0, len(members))
   386  	for _, m := range members {
   387  
   388  		chatMember := ChatMember{
   389  			ID: m,
   390  		}
   391  		chatMember.Admin = stringSliceContains(admins, m)
   392  		chatMembers = append(chatMembers, chatMember)
   393  	}
   394  	c.Members = chatMembers
   395  
   396  	// MembershipUpdates
   397  	c.MembershipUpdates = g.Events()
   398  }
   399  
   400  // NextClockAndTimestamp returns the next clock value
   401  // and the current timestamp
   402  func (c *Chat) NextClockAndTimestamp(timesource common.TimeSource) (uint64, uint64) {
   403  	clock := c.LastClockValue
   404  	timestamp := timesource.GetCurrentTime()
   405  	if clock == 0 || clock < timestamp {
   406  		clock = timestamp
   407  	} else {
   408  		clock = clock + 1
   409  	}
   410  	c.LastClockValue = clock
   411  
   412  	return clock, timestamp
   413  }
   414  
   415  func (c *Chat) UpdateFromMessage(message *common.Message, timesource common.TimeSource) error {
   416  	c.Timestamp = int64(timesource.GetCurrentTime())
   417  
   418  	// If the clock of the last message is lower, we set the message
   419  	if c.LastMessage == nil || c.LastMessage.Clock <= message.Clock {
   420  		c.LastMessage = message
   421  	}
   422  	// If the clock is higher we set the clock
   423  	if c.LastClockValue < message.Clock {
   424  		c.LastClockValue = message.Clock
   425  	}
   426  	return nil
   427  }
   428  
   429  func (c *Chat) UpdateFirstMessageTimestamp(timestamp uint32) bool {
   430  	if timestamp == c.FirstMessageTimestamp {
   431  		return false
   432  	}
   433  
   434  	// Do not allow to assign `Undefined`` or `NoMessage` to already set timestamp
   435  	if timestamp == FirstMessageTimestampUndefined ||
   436  		(timestamp == FirstMessageTimestampNoMessage &&
   437  			c.FirstMessageTimestamp != FirstMessageTimestampUndefined) {
   438  		return false
   439  	}
   440  
   441  	if c.FirstMessageTimestamp == FirstMessageTimestampUndefined ||
   442  		c.FirstMessageTimestamp == FirstMessageTimestampNoMessage ||
   443  		timestamp < c.FirstMessageTimestamp {
   444  		c.FirstMessageTimestamp = timestamp
   445  		return true
   446  	}
   447  
   448  	return false
   449  }
   450  
   451  // ChatMembershipUpdate represent an event on membership of the chat
   452  type ChatMembershipUpdate struct {
   453  	// Unique identifier for the event
   454  	ID string `json:"id"`
   455  	// Type indicates the kind of event
   456  	Type protobuf.MembershipUpdateEvent_EventType `json:"type"`
   457  	// Name represents the name in the event of changing name events
   458  	Name string `json:"name,omitempty"`
   459  	// Clock value of the event
   460  	ClockValue uint64 `json:"clockValue"`
   461  	// Signature of the event
   462  	Signature string `json:"signature"`
   463  	// Hex encoded public key of the creator of the event
   464  	From string `json:"from"`
   465  	// Target of the event for single-target events
   466  	Member string `json:"member,omitempty"`
   467  	// Target of the event for multi-target events
   468  	Members []string `json:"members,omitempty"`
   469  }
   470  
   471  // ChatMember represents a member who participates in a group chat
   472  type ChatMember struct {
   473  	// ID is the hex encoded public key of the member
   474  	ID string `json:"id"`
   475  	// Admin indicates if the member is an admin of the group chat
   476  	Admin bool `json:"admin"`
   477  }
   478  
   479  func (c ChatMember) PublicKey() (*ecdsa.PublicKey, error) {
   480  	return common.HexToPubkey(c.ID)
   481  }
   482  
   483  func oneToOneChatID(publicKey *ecdsa.PublicKey) string {
   484  	return types.EncodeHex(crypto.FromECDSAPub(publicKey))
   485  }
   486  
   487  func OneToOneFromPublicKey(pk *ecdsa.PublicKey, timesource common.TimeSource) *Chat {
   488  	chatID := types.EncodeHex(crypto.FromECDSAPub(pk))
   489  	newChat := CreateOneToOneChat(chatID[:8], pk, timesource)
   490  
   491  	return newChat
   492  }
   493  
   494  func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey, timesource common.TimeSource) *Chat {
   495  	timestamp := timesource.GetCurrentTime()
   496  	return &Chat{
   497  		ID:                       oneToOneChatID(publicKey),
   498  		Name:                     name,
   499  		Timestamp:                int64(timestamp),
   500  		ReadMessagesAtClockValue: 0,
   501  		Active:                   true,
   502  		Joined:                   int64(timestamp),
   503  		ChatType:                 ChatTypeOneToOne,
   504  		Highlight:                true,
   505  	}
   506  }
   507  
   508  func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat, timesource common.TimeSource) *Chat {
   509  	color := orgChat.Identity.Color
   510  	if color == "" {
   511  		color = chatColors[rand.Intn(len(chatColors))] // nolint: gosec
   512  	}
   513  
   514  	timestamp := timesource.GetCurrentTime()
   515  
   516  	// Populate community _channel_ members to _chat_ members
   517  	chatMembers := []ChatMember{}
   518  	for pubKey := range orgChat.Members {
   519  		chatMember := ChatMember{
   520  			ID:    pubKey,
   521  			Admin: false,
   522  		}
   523  		chatMembers = append(chatMembers, chatMember)
   524  	}
   525  
   526  	return &Chat{
   527  		CommunityID:              orgID,
   528  		CategoryID:               orgChat.CategoryId,
   529  		HideIfPermissionsNotMet:  orgChat.HideIfPermissionsNotMet,
   530  		Name:                     orgChat.Identity.DisplayName,
   531  		Description:              orgChat.Identity.Description,
   532  		Members:                  chatMembers,
   533  		Active:                   true,
   534  		Color:                    color,
   535  		Emoji:                    orgChat.Identity.Emoji,
   536  		ID:                       orgID + chatID,
   537  		Timestamp:                int64(timestamp),
   538  		Joined:                   int64(timestamp),
   539  		ReadMessagesAtClockValue: 0,
   540  		ChatType:                 ChatTypeCommunityChat,
   541  		FirstMessageTimestamp:    orgChat.Identity.FirstMessageTimestamp,
   542  		ViewersCanPostReactions:  orgChat.ViewersCanPostReactions,
   543  	}
   544  }
   545  
   546  func (c *Chat) CommunityChannelID() string {
   547  	return strings.TrimPrefix(c.ID, c.CommunityID)
   548  }
   549  
   550  func (c *Chat) DeepLink() string {
   551  	if c.OneToOne() {
   552  		return "status-app://p/" + c.ID
   553  	}
   554  	if c.PrivateGroupChat() {
   555  		return "status-app://g/args?a2=" + c.ID
   556  	}
   557  
   558  	if c.CommunityChat() {
   559  		communityChannelID := c.CommunityChannelID()
   560  		pubkey, err := types.DecodeHex(c.CommunityID)
   561  		if err != nil {
   562  			return ""
   563  		}
   564  
   565  		serializedCommunityID, err := utils.SerializePublicKey(pubkey)
   566  
   567  		if err != nil {
   568  			return ""
   569  		}
   570  
   571  		return "status-app://cc/" + communityChannelID + "#" + serializedCommunityID
   572  	}
   573  
   574  	if c.Public() {
   575  		return "status-app://" + c.ID
   576  	}
   577  
   578  	return ""
   579  }
   580  
   581  func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat {
   582  	var chats []*Chat
   583  	orgID := org.IDString()
   584  
   585  	for chatID, chat := range org.Chats() {
   586  		chats = append(chats, CreateCommunityChat(orgID, chatID, chat, timesource))
   587  	}
   588  	return chats
   589  }
   590  
   591  func CreatePublicChat(name string, timesource common.TimeSource) *Chat {
   592  	timestamp := timesource.GetCurrentTime()
   593  	return &Chat{
   594  		ID:                       name,
   595  		Name:                     name,
   596  		Active:                   true,
   597  		Timestamp:                int64(timestamp),
   598  		Joined:                   int64(timestamp),
   599  		ReadMessagesAtClockValue: 0,
   600  		Color:                    chatColors[rand.Intn(len(chatColors))], // nolint: gosec
   601  		ChatType:                 ChatTypePublic,
   602  		Members:                  []ChatMember{},
   603  	}
   604  }
   605  
   606  // Deprecated: buildProfileChatID shouldn't be used
   607  // and is only left here in case profile chat feature is re-introduced.
   608  func buildProfileChatID(publicKeyString string) string {
   609  	return "@" + publicKeyString
   610  }
   611  
   612  // Deprecated: CreateProfileChat shouldn't be used
   613  // and is only left here in case profile chat feature is re-introduced.
   614  func CreateProfileChat(pubkey string, timesource common.TimeSource) *Chat {
   615  	// Return nil to prevent usage of deprecated function
   616  	if deprecation.ChatProfileDeprecated {
   617  		return nil
   618  	}
   619  
   620  	id := buildProfileChatID(pubkey)
   621  	return &Chat{
   622  		ID:        id,
   623  		Name:      id,
   624  		Active:    true,
   625  		Timestamp: int64(timesource.GetCurrentTime()),
   626  		Joined:    int64(timesource.GetCurrentTime()),
   627  		Color:     chatColors[rand.Intn(len(chatColors))], // nolint: gosec
   628  		ChatType:  ChatTypeProfile,
   629  		Profile:   pubkey,
   630  	}
   631  }
   632  
   633  func CreateGroupChat(timesource common.TimeSource) Chat {
   634  	timestamp := timesource.GetCurrentTime()
   635  	synced := uint32(timestamp / 1000)
   636  
   637  	return Chat{
   638  		Active:                   true,
   639  		Color:                    chatColors[rand.Intn(len(chatColors))], // nolint: gosec
   640  		Timestamp:                int64(timestamp),
   641  		ReadMessagesAtClockValue: 0,
   642  		SyncedTo:                 synced,
   643  		SyncedFrom:               synced,
   644  		ChatType:                 ChatTypePrivateGroupChat,
   645  		Highlight:                true,
   646  	}
   647  }
   648  
   649  // Deprecated: CreateTimelineChat shouldn't be used
   650  // and is only left here in case profile chat feature is re-introduced.
   651  func CreateTimelineChat(timesource common.TimeSource) *Chat {
   652  	// Return nil to prevent usage of deprecated function
   653  	if deprecation.ChatTimelineDeprecated {
   654  		return nil
   655  	}
   656  
   657  	return &Chat{
   658  		ID:        timelineChatID,
   659  		Name:      "#" + timelineChatID,
   660  		Timestamp: int64(timesource.GetCurrentTime()),
   661  		Active:    true,
   662  		ChatType:  ChatTypeTimeline,
   663  	}
   664  }
   665  
   666  func stringSliceToPublicKeys(slice []string) ([]*ecdsa.PublicKey, error) {
   667  	result := make([]*ecdsa.PublicKey, len(slice))
   668  	for idx, item := range slice {
   669  		var err error
   670  		result[idx], err = common.HexToPubkey(item)
   671  		if err != nil {
   672  			return nil, err
   673  		}
   674  	}
   675  	return result, nil
   676  }
   677  
   678  func stringSliceContains(slice []string, item string) bool {
   679  	for _, s := range slice {
   680  		if s == item {
   681  			return true
   682  		}
   683  	}
   684  	return false
   685  }
   686  
   687  func GetChatContextFromChatType(chatType ChatType) ChatContext {
   688  	switch chatType {
   689  	case ChatTypeOneToOne, ChatTypePrivateGroupChat:
   690  		return privateChat
   691  	default:
   692  		return publicChat
   693  	}
   694  }