status-go/protocol/messenger_response.go
frank f123e98179
feat: fallback pairing seed (#5614)
* feat(pairing)!: Add extra parameters and remove v2 compatibility

This commit includes the following changes:

I have added a flag to maintain 2.29 compatibility.

Breaking change in connection string

The local pairing code that was parsing the connection string had a few non-upgradable features:

It was strictly checking the number of parameters, throwing an error if the number was different. This made it impossible to add parameters to it without breaking.
It was strictly checking the version number. This made increasing the version number impossible as older client would just refuse to connect.
The code has been changed so that:

Two parameters have been added, installation-id and key-uid. Those are needed for the fallback flow.
I have also removed version from the payload, since it wasn't used.

This means that we don't support v1 anymore. V2 parsing is supported . Going forward there's a clear strategy on how to update the protocol (append parameters, don't change existing one).

https://www.youtube.com/watch?v=oyLBGkS5ICk Is a must watch video for understanding the strategy

Changed MessengerResponse to use internally a map of installations rather than an array (minor)
Just moving towards maps as arrays tend to lead to subtle bugs.

Moved pairing methods to messenger_pairing.go
Just moved some methods

Added 2 new methods for the fallback flow
FinishPairingThroughSeedPhraseProcess
https://github.com/status-im/status-go/pull/5567/files#diff-1ad620b07fa3bd5fbc96c9f459d88829938a162bf1aaf41c61dea6e38b488d54R29

EnableAndSyncInstallation

https://github.com/status-im/status-go/pull/5567/files#diff-1ad620b07fa3bd5fbc96c9f459d88829938a162bf1aaf41c61dea6e38b488d54R18

Flow for clients
Client A1 is logged in
Client A2 is logged out

Client A1 shows a QR code
Client A2 scans a QR code
If connection fails on A2, the user will be prompted to enter a seed phrase.
If the generated account matches the key-uid from the QR code, A2 should call FinishPairingThroughSeedPhraseProcess with the installation id passed in the QR code. This will send installation information over waku. The user should be shown its own installation id and prompted to check the other device.
Client A1 will receive new installation data through waku, if they are still on the qr code page, they should show a popup to the user showing the received installation id, and a way to Enable and Sync, which should call the EnableAndSyncInstallation endpoint. This should finish the fallback syncing flow.
Current issues
Currently I haven't tested that all the data is synced after finishing the flow. I see that the two devices are paired correctly, but for example the DisplayName is not changed on the receiving device. I haven't had time to look into it further.

* test_: add more test for connection string parser

* fix_: fix panic when parse old connection string

* test_: add comments for TestMessengerPairAfterSeedPhrase

* fix_: correct error description

* feat_:rename FinishPairingThroughSeedPhraseProcess to EnableInstallationAndPair

* fix_: delete leftover

* fix_: add UniqueKey method

* fix_: unify the response for InputConnectionStringForBootstrapping

* fix_: remove fields installationID and keyUID in GethStatusBackend

* fix_: rename messenger_pairing to messenger_pairing_and_syncing

---------

Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2024-07-30 17:14:05 +08:00

917 lines
31 KiB
Go

package protocol
import (
"encoding/json"
"golang.org/x/exp/maps"
ensservice "github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/services/browsers"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/appmetrics"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/settings"
walletsettings "github.com/status-im/status-go/multiaccounts/settings_wallet"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/discord"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/storenodes"
"github.com/status-im/status-go/protocol/verification"
localnotifications "github.com/status-im/status-go/services/local-notifications"
"github.com/status-im/status-go/services/mailservers"
)
type RemovedMessage struct {
ChatID string `json:"chatId"`
MessageID string `json:"messageId"`
DeletedBy string `json:"deletedBy,omitempty"`
}
type ClearedHistory struct {
ChatID string `json:"chatId"`
ClearedAt uint64 `json:"clearedAt"`
}
type SeenUnseenMessages struct {
ChatID string `json:"chatId"`
Count uint64 `json:"count"`
CountWithMentions uint64 `json:"countWithMentions"`
Seen bool `json:"seen"`
}
type MessengerResponse struct {
Contacts []*Contact
Invitations []*GroupChatInvitation
CommunityChanges []*communities.CommunityChanges
AnonymousMetrics []*appmetrics.AppMetric
Mailservers []mailservers.Mailserver
CommunityStorenodes []storenodes.Storenode
Bookmarks []*browsers.Bookmark
Settings []*settings.SyncSettingField
IdentityImages []images.IdentityImage
CustomizationColor string
WatchOnlyAccounts []*accounts.Account
Keypairs []*accounts.Keypair
AccountsPositions []*accounts.Account
TokenPreferences []walletsettings.TokenPreferences
CollectiblePreferences []walletsettings.CollectiblePreferences
DiscordCategories []*discord.Category
DiscordChannels []*discord.Channel
DiscordOldestMessageTimestamp int
BackupHandled bool
// notifications a list of notifications derived from messenger events
// that are useful to notify the user about
installations map[string]*multidevice.Installation
notifications map[string]*localnotifications.Notification
requestsToJoinCommunity map[string]*communities.RequestToJoin
chats map[string]*Chat
removedChats map[string]bool
removedMessages map[string]*RemovedMessage
deletedMessages map[string]string
communities map[string]*communities.Community
communitiesSettings map[string]*communities.CommunitySettings
activityCenterNotifications map[string]*ActivityCenterNotification
activityCenterState *ActivityCenterState
messages map[string]*common.Message
pinMessages map[string]*common.PinMessage
discordMessages map[string]*protobuf.DiscordMessage
discordMessageAttachments map[string]*protobuf.DiscordMessageAttachment
discordMessageAuthors map[string]*protobuf.DiscordMessageAuthor
currentStatus *UserStatus
statusUpdates map[string]UserStatus
clearedHistories map[string]*ClearedHistory
verificationRequests map[string]*verification.Request
trustStatus map[string]verification.TrustStatus
emojiReactions map[string]*EmojiReaction
savedAddresses map[string]*wallet.SavedAddress
ensUsernameDetails []*ensservice.UsernameDetail
updatedProfileShowcaseContactIDs map[string]bool
seenAndUnseenMessages map[string]*SeenUnseenMessages
}
func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
responseItem := struct {
Chats []*Chat `json:"chats,omitempty"`
RemovedChats []string `json:"removedChats,omitempty"`
RemovedMessages []*RemovedMessage `json:"removedMessages,omitempty"`
DeletedMessages map[string][]string `json:"deletedMessages,omitempty"`
Messages []*common.Message `json:"messages,omitempty"`
Contacts []*Contact `json:"contacts,omitempty"`
Installations []*multidevice.Installation `json:"installations,omitempty"`
PinMessages []*common.PinMessage `json:"pinMessages,omitempty"`
EmojiReactions []*EmojiReaction `json:"emojiReactions,omitempty"`
Invitations []*GroupChatInvitation `json:"invitations,omitempty"`
CommunityChanges []*communities.CommunityChanges `json:"communityChanges,omitempty"`
RequestsToJoinCommunity []*communities.RequestToJoin `json:"requestsToJoinCommunity,omitempty"`
Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"`
CommunityStorenodes []storenodes.Storenode `json:"communityStorenodes,omitempty"`
Bookmarks []*browsers.Bookmark `json:"bookmarks,omitempty"`
ClearedHistories []*ClearedHistory `json:"clearedHistories,omitempty"`
VerificationRequests []*verification.Request `json:"verificationRequests,omitempty"`
TrustStatus map[string]verification.TrustStatus `json:"trustStatus,omitempty"`
// Notifications a list of notifications derived from messenger events
// that are useful to notify the user about
Notifications []*localnotifications.Notification `json:"notifications"`
Communities []*communities.Community `json:"communities,omitempty"`
CommunitiesSettings []*communities.CommunitySettings `json:"communitiesSettings,omitempty"`
ActivityCenterNotifications []*ActivityCenterNotification `json:"activityCenterNotifications,omitempty"`
ActivityCenterState *ActivityCenterState `json:"activityCenterState,omitempty"`
CurrentStatus *UserStatus `json:"currentStatus,omitempty"`
StatusUpdates []UserStatus `json:"statusUpdates,omitempty"`
Settings []*settings.SyncSettingField `json:"settings,omitempty"`
IdentityImages []images.IdentityImage `json:"identityImages,omitempty"`
CustomizationColor string `json:"customizationColor,omitempty"`
WatchOnlyAccounts []*accounts.Account `json:"watchOnlyAccounts,omitempty"`
Keypairs []*accounts.Keypair `json:"keypairs,omitempty"`
AccountsPositions []*accounts.Account `json:"accountsPositions,omitempty"`
TokenPreferences []walletsettings.TokenPreferences `json:"tokenPreferences,omitempty"`
CollectiblePreferences []walletsettings.CollectiblePreferences `json:"collectiblePreferences,omitempty"`
DiscordCategories []*discord.Category `json:"discordCategories,omitempty"`
DiscordChannels []*discord.Channel `json:"discordChannels,omitempty"`
DiscordOldestMessageTimestamp int `json:"discordOldestMessageTimestamp"`
DiscordMessages []*protobuf.DiscordMessage `json:"discordMessages,omitempty"`
DiscordMessageAttachments []*protobuf.DiscordMessageAttachment `json:"discordMessageAtachments,omitempty"`
SavedAddresses []*wallet.SavedAddress `json:"savedAddresses,omitempty"`
EnsUsernameDetails []*ensservice.UsernameDetail `json:"ensUsernameDetails,omitempty"`
UpdatedProfileShowcaseContactIDs []string `json:"updatedProfileShowcaseContactIDs,omitempty"`
SeenAndUnseenMessages []*SeenUnseenMessages `json:"seenAndUnseenMessages,omitempty"`
}{
Contacts: r.Contacts,
Installations: r.Installations(),
Invitations: r.Invitations,
CommunityChanges: r.CommunityChanges,
RequestsToJoinCommunity: r.RequestsToJoinCommunity(),
Mailservers: r.Mailservers,
CommunityStorenodes: r.CommunityStorenodes,
Bookmarks: r.Bookmarks,
CurrentStatus: r.currentStatus,
Settings: r.Settings,
IdentityImages: r.IdentityImages,
CustomizationColor: r.CustomizationColor,
WatchOnlyAccounts: r.WatchOnlyAccounts,
Keypairs: r.Keypairs,
AccountsPositions: r.AccountsPositions,
TokenPreferences: r.TokenPreferences,
CollectiblePreferences: r.CollectiblePreferences,
Messages: r.Messages(),
VerificationRequests: r.VerificationRequests(),
SavedAddresses: r.SavedAddresses(),
Notifications: r.Notifications(),
Chats: r.Chats(),
Communities: r.Communities(),
CommunitiesSettings: r.CommunitiesSettings(),
RemovedChats: r.RemovedChats(),
RemovedMessages: r.RemovedMessages(),
DeletedMessages: r.DeletedMessagesInChats(),
ClearedHistories: r.ClearedHistories(),
ActivityCenterNotifications: r.ActivityCenterNotifications(),
ActivityCenterState: r.ActivityCenterState(),
PinMessages: r.PinMessages(),
EmojiReactions: r.EmojiReactions(),
StatusUpdates: r.StatusUpdates(),
DiscordCategories: r.DiscordCategories,
DiscordChannels: r.DiscordChannels,
DiscordOldestMessageTimestamp: r.DiscordOldestMessageTimestamp,
EnsUsernameDetails: r.EnsUsernameDetails(),
UpdatedProfileShowcaseContactIDs: r.GetUpdatedProfileShowcaseContactIDs(),
SeenAndUnseenMessages: r.GetSeenAndUnseenMessages(),
}
responseItem.TrustStatus = r.TrustStatus()
return json.Marshal(responseItem)
}
func (r *MessengerResponse) Chats() []*Chat {
var chats []*Chat
for _, chat := range r.chats {
chats = append(chats, chat)
}
return chats
}
func (r *MessengerResponse) Installations() []*multidevice.Installation {
var is []*multidevice.Installation
for _, i := range r.installations {
is = append(is, i)
}
return is
}
func (r *MessengerResponse) RemovedChats() []string {
var chats []string
for chatID := range r.removedChats {
chats = append(chats, chatID)
}
return chats
}
func (r *MessengerResponse) RemovedMessages() []*RemovedMessage {
var messages []*RemovedMessage
for messageID := range r.removedMessages {
messages = append(messages, r.removedMessages[messageID])
}
return messages
}
func (r *MessengerResponse) DeletedMessages() map[string]string {
return r.deletedMessages
}
func (r *MessengerResponse) DeletedMessagesInChats() map[string][]string {
deletedMessagesInChats := make(map[string][]string)
for messageID, chatID := range r.deletedMessages {
deletedMessagesInChats[chatID] = append(deletedMessagesInChats[chatID], messageID)
}
return deletedMessagesInChats
}
func (r *MessengerResponse) ClearedHistories() []*ClearedHistory {
var clearedHistories []*ClearedHistory
for chatID := range r.clearedHistories {
clearedHistories = append(clearedHistories, r.clearedHistories[chatID])
}
return clearedHistories
}
func (r *MessengerResponse) Communities() []*communities.Community {
var communities []*communities.Community
for _, c := range r.communities {
communities = append(communities, c)
}
return communities
}
func (r *MessengerResponse) CommunitiesSettings() []*communities.CommunitySettings {
var settings []*communities.CommunitySettings
for _, s := range r.communitiesSettings {
settings = append(settings, s)
}
return settings
}
func (r *MessengerResponse) Notifications() []*localnotifications.Notification {
var notifications []*localnotifications.Notification
for _, n := range r.notifications {
notifications = append(notifications, n)
}
return notifications
}
func (r *MessengerResponse) VerificationRequests() []*verification.Request {
var verifications []*verification.Request
for _, v := range r.verificationRequests {
verifications = append(verifications, v)
}
return verifications
}
func (r *MessengerResponse) PinMessages() []*common.PinMessage {
var pinMessages []*common.PinMessage
for _, pm := range r.pinMessages {
pinMessages = append(pinMessages, pm)
}
return pinMessages
}
func (r *MessengerResponse) TrustStatus() map[string]verification.TrustStatus {
if len(r.trustStatus) == 0 {
return nil
}
result := make(map[string]verification.TrustStatus)
for contactID, trustStatus := range r.trustStatus {
result[contactID] = trustStatus
}
return result
}
func (r *MessengerResponse) StatusUpdates() []UserStatus {
var userStatus []UserStatus
for pk, s := range r.statusUpdates {
s.PublicKey = pk
userStatus = append(userStatus, s)
}
return userStatus
}
func (r *MessengerResponse) IsEmpty() bool {
return len(r.chats)+
len(r.messages)+
len(r.pinMessages)+
len(r.Contacts)+
len(r.Bookmarks)+
len(r.clearedHistories)+
len(r.Settings)+
len(r.installations)+
len(r.Invitations)+
len(r.emojiReactions)+
len(r.communities)+
len(r.CommunityChanges)+
len(r.removedChats)+
len(r.removedMessages)+
len(r.deletedMessages)+
len(r.Mailservers)+
len(r.CommunityStorenodes)+
len(r.IdentityImages)+
len(r.WatchOnlyAccounts)+
len(r.Keypairs)+
len(r.AccountsPositions)+
len(r.TokenPreferences)+
len(r.CollectiblePreferences)+
len(r.notifications)+
len(r.statusUpdates)+
len(r.activityCenterNotifications)+
len(r.trustStatus)+
len(r.verificationRequests)+
len(r.requestsToJoinCommunity)+
len(r.savedAddresses)+
len(r.updatedProfileShowcaseContactIDs)+
len(r.seenAndUnseenMessages)+
len(r.ensUsernameDetails) == 0 &&
r.currentStatus == nil &&
r.activityCenterState == nil &&
r.CustomizationColor == ""
}
// Merge takes another response and appends the new Chats & new Messages and replaces
// the existing Messages & Chats if they have the same ID
func (r *MessengerResponse) Merge(response *MessengerResponse) error {
if len(response.Invitations)+
len(response.Mailservers)+
len(response.clearedHistories)+
len(response.DiscordChannels)+
len(response.DiscordCategories) != 0 {
return ErrNotImplemented
}
r.AddChats(response.Chats())
r.AddRemovedChats(response.RemovedChats())
r.AddRemovedMessages(response.RemovedMessages())
r.MergeDeletedMessages(response.DeletedMessages())
r.AddNotifications(response.Notifications())
r.AddMessages(response.Messages())
r.AddContacts(response.Contacts)
r.AddCommunities(response.Communities())
r.UpdateCommunitySettings(response.CommunitiesSettings())
r.AddPinMessages(response.PinMessages())
r.AddVerificationRequests(response.VerificationRequests())
r.AddTrustStatuses(response.trustStatus)
r.AddActivityCenterNotifications(response.ActivityCenterNotifications())
r.SetActivityCenterState(response.ActivityCenterState())
r.AddEmojiReactions(response.EmojiReactions())
r.AddInstallations(response.Installations())
r.AddSavedAddresses(response.SavedAddresses())
r.AddEnsUsernameDetails(response.EnsUsernameDetails())
r.AddRequestsToJoinCommunity(response.RequestsToJoinCommunity())
r.AddBookmarks(response.GetBookmarks())
r.AddSeveralUpdatedProfileShowcaseContactIDs(response.GetUpdatedProfileShowcaseContactIDs())
r.AddSeveralSeenAndUnseenMessages(response.GetSeenAndUnseenMessages())
r.CommunityChanges = append(r.CommunityChanges, response.CommunityChanges...)
r.BackupHandled = response.BackupHandled
r.CustomizationColor = response.CustomizationColor
r.WatchOnlyAccounts = append(r.WatchOnlyAccounts, response.WatchOnlyAccounts...)
r.Keypairs = append(r.Keypairs, response.Keypairs...)
r.AccountsPositions = append(r.AccountsPositions, response.AccountsPositions...)
r.TokenPreferences = append(r.TokenPreferences, response.TokenPreferences...)
r.CollectiblePreferences = append(r.CollectiblePreferences, response.CollectiblePreferences...)
return nil
}
func (r *MessengerResponse) AddCommunities(communities []*communities.Community) {
for _, overrideCommunity := range communities {
r.AddCommunity(overrideCommunity)
}
}
func (r *MessengerResponse) AddCommunity(c *communities.Community) {
if r.communities == nil {
r.communities = make(map[string]*communities.Community)
}
r.communities[c.IDString()] = c
}
func (r *MessengerResponse) AddCommunitySettings(c *communities.CommunitySettings) {
if r.communitiesSettings == nil {
r.communitiesSettings = make(map[string]*communities.CommunitySettings)
}
r.communitiesSettings[c.CommunityID] = c
}
func (r *MessengerResponse) UpdateCommunitySettings(communitySettings []*communities.CommunitySettings) {
for _, communitySetting := range communitySettings {
r.AddCommunitySettings(communitySetting)
}
}
func (r *MessengerResponse) AddRequestsToJoinCommunity(requestsToJoin []*communities.RequestToJoin) {
for _, rq := range requestsToJoin {
r.AddRequestToJoinCommunity(rq)
}
}
func (r *MessengerResponse) AddRequestToJoinCommunity(requestToJoin *communities.RequestToJoin) {
if r.requestsToJoinCommunity == nil {
r.requestsToJoinCommunity = make(map[string]*communities.RequestToJoin)
}
r.requestsToJoinCommunity[requestToJoin.ID.String()] = requestToJoin
}
func (r *MessengerResponse) RequestsToJoinCommunity() []*communities.RequestToJoin {
var rs []*communities.RequestToJoin
for _, r := range r.requestsToJoinCommunity {
rs = append(rs, r)
}
return rs
}
func (r *MessengerResponse) AddSetting(s *settings.SyncSettingField) {
r.Settings = append(r.Settings, s)
}
func (r *MessengerResponse) AddBookmark(bookmark *browsers.Bookmark) {
r.Bookmarks = append(r.Bookmarks, bookmark)
}
func (r *MessengerResponse) AddBookmarks(bookmarks []*browsers.Bookmark) {
for _, b := range bookmarks {
r.AddBookmark(b)
}
}
func (r *MessengerResponse) GetBookmarks() []*browsers.Bookmark {
return r.Bookmarks
}
func (r *MessengerResponse) AddVerificationRequest(vr *verification.Request) {
if r.verificationRequests == nil {
r.verificationRequests = make(map[string]*verification.Request)
}
r.verificationRequests[vr.ID] = vr
}
func (r *MessengerResponse) AddVerificationRequests(vrs []*verification.Request) {
for _, vr := range vrs {
r.AddVerificationRequest(vr)
}
}
func (r *MessengerResponse) AddTrustStatus(contactID string, trustStatus verification.TrustStatus) {
if r.trustStatus == nil {
r.trustStatus = make(map[string]verification.TrustStatus)
}
r.trustStatus[contactID] = trustStatus
}
func (r *MessengerResponse) AddTrustStatuses(ts map[string]verification.TrustStatus) {
if r.trustStatus == nil {
r.trustStatus = make(map[string]verification.TrustStatus)
}
for k, v := range ts {
r.trustStatus[k] = v
}
}
func (r *MessengerResponse) AddChat(c *Chat) {
if r.chats == nil {
r.chats = make(map[string]*Chat)
}
r.chats[c.ID] = c
}
func (r *MessengerResponse) AddChats(chats []*Chat) {
for _, c := range chats {
r.AddChat(c)
}
}
func (r *MessengerResponse) AddEmojiReactions(ers []*EmojiReaction) {
for _, e := range ers {
r.AddEmojiReaction(e)
}
}
func (r *MessengerResponse) AddEmojiReaction(er *EmojiReaction) {
if r.emojiReactions == nil {
r.emojiReactions = make(map[string]*EmojiReaction)
}
r.emojiReactions[er.ID()] = er
}
func (r *MessengerResponse) EmojiReactions() []*EmojiReaction {
var ers []*EmojiReaction
for _, er := range r.emojiReactions {
ers = append(ers, er)
}
return ers
}
func (r *MessengerResponse) AddSavedAddresses(ers []*wallet.SavedAddress) {
for _, e := range ers {
r.AddSavedAddress(e)
}
}
func (r *MessengerResponse) AddSavedAddress(er *wallet.SavedAddress) {
if r.savedAddresses == nil {
r.savedAddresses = make(map[string]*wallet.SavedAddress)
}
r.savedAddresses[er.ID()] = er
}
func (r *MessengerResponse) SavedAddresses() []*wallet.SavedAddress {
return maps.Values(r.savedAddresses)
}
func (r *MessengerResponse) AddEnsUsernameDetail(detail *ensservice.UsernameDetail) {
r.ensUsernameDetails = append(r.ensUsernameDetails, detail)
}
func (r *MessengerResponse) AddEnsUsernameDetails(details []*ensservice.UsernameDetail) {
r.ensUsernameDetails = append(r.ensUsernameDetails, details...)
}
func (r *MessengerResponse) EnsUsernameDetails() []*ensservice.UsernameDetail {
return r.ensUsernameDetails
}
func (r *MessengerResponse) AddNotification(n *localnotifications.Notification) {
if r.notifications == nil {
r.notifications = make(map[string]*localnotifications.Notification)
}
r.notifications[n.ID.String()] = n
}
func (r *MessengerResponse) ClearNotifications() {
r.notifications = nil
}
func (r *MessengerResponse) AddNotifications(notifications []*localnotifications.Notification) {
for _, c := range notifications {
r.AddNotification(c)
}
}
func (r *MessengerResponse) AddRemovedChats(chats []string) {
for _, c := range chats {
r.AddRemovedChat(c)
}
}
func (r *MessengerResponse) AddRemovedChat(chatID string) {
if r.removedChats == nil {
r.removedChats = make(map[string]bool)
}
r.removedChats[chatID] = true
}
func (r *MessengerResponse) AddRemovedMessages(messages []*RemovedMessage) {
for _, m := range messages {
r.AddRemovedMessage(m)
}
}
func (r *MessengerResponse) AddRemovedMessage(rm *RemovedMessage) {
if r.removedMessages == nil {
r.removedMessages = make(map[string]*RemovedMessage)
}
r.removedMessages[rm.MessageID] = rm
// Remove original message from the map
if len(r.messages) != 0 && r.messages[rm.MessageID] != nil {
delete(r.messages, rm.MessageID)
}
}
func (r *MessengerResponse) AddDeletedMessages(messagesToAdd []*protobuf.DeleteCommunityMemberMessage) {
if r.deletedMessages == nil {
r.deletedMessages = make(map[string]string)
}
for _, message := range messagesToAdd {
r.deletedMessages[message.Id] = message.ChatId
}
}
func (r *MessengerResponse) MergeDeletedMessages(messagesToAdd map[string]string) {
if r.deletedMessages == nil {
r.deletedMessages = make(map[string]string)
}
for messageID, chatID := range messagesToAdd {
r.deletedMessages[messageID] = chatID
}
}
func (r *MessengerResponse) AddClearedHistory(ch *ClearedHistory) {
if r.clearedHistories == nil {
r.clearedHistories = make(map[string]*ClearedHistory)
}
existingClearedHistory, ok := r.clearedHistories[ch.ChatID]
if !ok || existingClearedHistory.ClearedAt < ch.ClearedAt {
r.clearedHistories[ch.ChatID] = ch
}
}
func (r *MessengerResponse) AddActivityCenterNotifications(ns []*ActivityCenterNotification) {
for _, n := range ns {
r.AddActivityCenterNotification(n)
}
}
func (r *MessengerResponse) AddActivityCenterNotification(n *ActivityCenterNotification) {
if r.activityCenterNotifications == nil {
r.activityCenterNotifications = make(map[string]*ActivityCenterNotification)
}
r.activityCenterNotifications[n.ID.String()] = n
}
func (r *MessengerResponse) RemoveActivityCenterNotification(id string) bool {
if r.activityCenterNotifications == nil {
return false
}
if _, ok := r.activityCenterNotifications[id]; ok {
delete(r.activityCenterNotifications, id)
return true
}
return false
}
func (r *MessengerResponse) ActivityCenterNotifications() []*ActivityCenterNotification {
var ns []*ActivityCenterNotification
for _, n := range r.activityCenterNotifications {
ns = append(ns, n)
}
return ns
}
func (r *MessengerResponse) SetActivityCenterState(activityCenterState *ActivityCenterState) {
r.activityCenterState = activityCenterState
}
func (r *MessengerResponse) ActivityCenterState() *ActivityCenterState {
return r.activityCenterState
}
func (r *MessengerResponse) AddPinMessage(pm *common.PinMessage) {
if r.pinMessages == nil {
r.pinMessages = make(map[string]*common.PinMessage)
}
r.pinMessages[pm.ID] = pm
}
func (r *MessengerResponse) AddPinMessages(pms []*common.PinMessage) {
for _, pm := range pms {
r.AddPinMessage(pm)
}
}
func (r *MessengerResponse) SetCurrentStatus(status UserStatus) {
r.currentStatus = &status
}
func (r *MessengerResponse) AddStatusUpdate(upd UserStatus) {
if r.statusUpdates == nil {
r.statusUpdates = make(map[string]UserStatus)
}
r.statusUpdates[upd.PublicKey] = upd
}
func (r *MessengerResponse) DiscordMessages() []*protobuf.DiscordMessage {
var ms []*protobuf.DiscordMessage
for _, m := range r.discordMessages {
ms = append(ms, m)
}
return ms
}
func (r *MessengerResponse) DiscordMessageAuthors() []*protobuf.DiscordMessageAuthor {
var authors []*protobuf.DiscordMessageAuthor
for _, author := range r.discordMessageAuthors {
authors = append(authors, author)
}
return authors
}
func (r *MessengerResponse) DiscordMessageAttachments() []*protobuf.DiscordMessageAttachment {
var attachments []*protobuf.DiscordMessageAttachment
for _, a := range r.discordMessageAttachments {
attachments = append(attachments, a)
}
return attachments
}
// Messages extracts the messages from the response and returns them as a slice.
// Since 'r.messages' is a map, the order of messages in the resulting slice is not
// guaranteed and can vary with each call to this method. This is inherent to Go's map
// iteration behavior, which does not define a sequence for the order of map elements.
// Consumers should not depend on the ordering of messages in the slice for any logic
// that requires consistent ordering, as map iteration order can change when keys are
// added or deleted. Consider sorting the slice after retrieval if a specific order is needed.
func (r *MessengerResponse) Messages() []*common.Message {
var ms []*common.Message
for _, m := range r.messages {
ms = append(ms, m)
}
return ms
}
func (r *MessengerResponse) AddDiscordMessageAuthor(author *protobuf.DiscordMessageAuthor) {
if r.discordMessageAuthors == nil {
r.discordMessageAuthors = make(map[string]*protobuf.DiscordMessageAuthor)
}
r.discordMessageAuthors[author.Id] = author
}
func (r *MessengerResponse) AddDiscordMessageAttachment(attachment *protobuf.DiscordMessageAttachment) {
if r.discordMessageAttachments == nil {
r.discordMessageAttachments = make(map[string]*protobuf.DiscordMessageAttachment)
}
r.discordMessageAttachments[attachment.Id] = attachment
}
func (r *MessengerResponse) AddDiscordMessageAttachments(attachments []*protobuf.DiscordMessageAttachment) {
for _, attachment := range attachments {
r.AddDiscordMessageAttachment(attachment)
}
}
func (r *MessengerResponse) AddDiscordMessage(message *protobuf.DiscordMessage) {
if r.discordMessages == nil {
r.discordMessages = make(map[string]*protobuf.DiscordMessage)
}
r.discordMessages[message.Id] = message
}
func (r *MessengerResponse) AddMessages(ms []*common.Message) {
for _, m := range ms {
r.AddMessage(m)
}
}
func (r *MessengerResponse) AddMessage(message *common.Message) {
if r.messages == nil {
r.messages = make(map[string]*common.Message)
}
if message.Deleted {
return
}
r.messages[message.ID] = message
}
func (r *MessengerResponse) AddContact(c *Contact) {
for idx, c1 := range r.Contacts {
if c1.ID == c.ID {
r.Contacts[idx] = c
return
}
}
r.Contacts = append(r.Contacts, c)
}
func (r *MessengerResponse) AddContacts(contacts []*Contact) {
for idx := range contacts {
r.AddContact(contacts[idx])
}
}
func (r *MessengerResponse) AddInstallation(i *multidevice.Installation) {
if len(r.installations) == 0 {
r.installations = make(map[string]*multidevice.Installation)
}
r.installations[i.UniqueKey()] = i
}
func (r *MessengerResponse) AddInstallations(installations []*multidevice.Installation) {
for idx := range installations {
r.AddInstallation(installations[idx])
}
}
func (r *MessengerResponse) SetMessages(messages []*common.Message) {
r.messages = make(map[string]*common.Message)
r.AddMessages(messages)
}
func (r *MessengerResponse) GetMessage(messageID string) *common.Message {
if r.messages == nil {
return nil
}
return r.messages[messageID]
}
func (r *MessengerResponse) AddDiscordCategory(dc *discord.Category) {
for idx, c := range r.DiscordCategories {
if dc.ID == c.ID {
r.DiscordCategories[idx] = dc
return
}
}
r.DiscordCategories = append(r.DiscordCategories, dc)
}
func (r *MessengerResponse) AddDiscordChannel(dc *discord.Channel) {
for idx, c := range r.DiscordChannels {
if dc.ID == c.ID {
r.DiscordChannels[idx] = dc
return
}
}
r.DiscordChannels = append(r.DiscordChannels, dc)
}
func (r *MessengerResponse) HasDiscordCategory(id string) bool {
for _, c := range r.DiscordCategories {
if id == c.ID {
return true
}
}
return false
}
func (r *MessengerResponse) HasDiscordChannel(id string) bool {
for _, c := range r.DiscordChannels {
if id == c.ID {
return true
}
}
return false
}
func (r *MessengerResponse) AddSeveralUpdatedProfileShowcaseContactIDs(contactIDs []string) {
for _, contactID := range contactIDs {
r.AddUpdatedProfileShowcaseContactID(contactID)
}
}
func (r *MessengerResponse) AddUpdatedProfileShowcaseContactID(contactID string) {
if r.updatedProfileShowcaseContactIDs == nil {
r.updatedProfileShowcaseContactIDs = make(map[string]bool)
}
if _, exists := r.updatedProfileShowcaseContactIDs[contactID]; exists {
return
}
r.updatedProfileShowcaseContactIDs[contactID] = true
}
func (r *MessengerResponse) GetUpdatedProfileShowcaseContactIDs() []string {
var contactIDs []string
for contactID := range r.updatedProfileShowcaseContactIDs {
contactIDs = append(contactIDs, contactID)
}
return contactIDs
}
func (r *MessengerResponse) AddSeveralSeenAndUnseenMessages(messages []*SeenUnseenMessages) {
for _, message := range messages {
r.AddSeenAndUnseenMessages(message)
}
}
func (r *MessengerResponse) AddSeenAndUnseenMessages(message *SeenUnseenMessages) {
if r.seenAndUnseenMessages == nil {
r.seenAndUnseenMessages = make(map[string]*SeenUnseenMessages)
}
r.seenAndUnseenMessages[message.ChatID] = message
}
func (r *MessengerResponse) GetSeenAndUnseenMessages() []*SeenUnseenMessages {
var messages []*SeenUnseenMessages
for _, message := range r.seenAndUnseenMessages {
messages = append(messages, message)
}
return messages
}