diff --git a/status/chat.nim b/status/chat.nim index 7d55632..a0e8f8b 100644 --- a/status/chat.nim +++ b/status/chat.nim @@ -1,5 +1,6 @@ import json, strutils, sequtils, tables, chronicles, times, sugar, algorithm import statusgo_backend/chat as status_chat +import statusgo_backend/contacts as status_contacts import statusgo_backend/chatCommands as status_chat_commands import types/[message, status_update, activity_center_notification, sticker, removed_message] @@ -31,7 +32,6 @@ type chats*: seq[Chat] messages*: seq[Message] pinnedMessages*: seq[Message] - contacts*: seq[Profile] emojiReactions*: seq[Reaction] communities*: seq[Community] communityMembershipRequests*: seq[CommunityMembershipRequest] @@ -80,7 +80,6 @@ type ChatModel* = ref object events*: EventEmitter communitiesToFetch*: seq[string] mailserverReady*: bool - contacts*: Table[string, Profile] channels*: Table[string, Chat] msgCursor: Table[string, string] pinnedMsgCursor: Table[string, string] @@ -93,13 +92,20 @@ proc newChatModel*(events: EventEmitter): ChatModel = result.events = events result.mailserverReady = false result.communitiesToFetch = @[] - result.contacts = initTable[string, Profile]() result.channels = initTable[string, Chat]() result.msgCursor = initTable[string, string]() result.pinnedMsgCursor = initTable[string, string]() result.emojiCursor = initTable[string, string]() result.lastMessageTimestamps = initTable[string, int64]() +proc getContacts*(self: ChatModel): Table[string, Profile] = + let (index, usedCache) = status_contacts.getContactsIndex() + if not usedCache: + let (contacts, _) = status_contacts.getContacts() + self.events.emit("contactUpdate", ContactUpdateArgs(contacts: contacts)) + + return index + proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest], pinnedMessages: seq[Message], activityCenterNotifications: seq[ActivityCenterNotification], statusUpdates: seq[StatusUpdate], deletedMessages: seq[RemovedMessage]) = for chat in chats: self.channels[chat.id] = chat @@ -113,7 +119,7 @@ proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiRea if self.lastMessageTimestamps[chatId] > ts: self.lastMessageTimestamps[chatId] = ts - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages,chats: chats, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages, activityCenterNotifications: activityCenterNotifications, statusUpdates: statusUpdates, deletedMessages: deletedMessages)) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages,chats: chats, emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages, activityCenterNotifications: activityCenterNotifications, statusUpdates: statusUpdates, deletedMessages: deletedMessages)) proc parseChatResponse(self: ChatModel, response: string): (seq[Chat], seq[Message]) = var parsedResponse = parseJson(response) @@ -131,7 +137,7 @@ proc parseChatResponse(self: ChatModel, response: string): (seq[Chat], seq[Messa proc emitUpdate(self: ChatModel, response: string) = var (chats, messages) = self.parseChatResponse(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats)) proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) @@ -225,11 +231,6 @@ proc createPublicChat*(self: ChatModel, chatId: string) = self.emitTopicAndJoin(chat) -proc updateContacts*(self: ChatModel, contacts: seq[Profile]) = - for c in contacts: - self.contacts[c.id] = c - self.events.emit("chatUpdate", ChatUpdateArgs(contacts: contacts)) - proc requestMissingCommunityInfos*(self: ChatModel) = if (self.communitiesToFetch.len == 0): return @@ -252,7 +253,8 @@ proc sortChats(x, y: chat_type.Chat): int = proc init*(self: ChatModel, pubKey: string) = self.publicKey = pubKey - var contacts = getAddedContacts() + var (contacts, _) = status_contacts.getContacts() + contacts = contacts.filter(c => c.systemTags.contains(contactAdded)) var chatList = status_chat.loadChats() chatList.sort(sortChats) @@ -295,15 +297,10 @@ proc init*(self: ChatModel, pubKey: string) = self.events.emit("chatsLoaded", ChatArgs(chats: chatList)) - self.events.once("mailserverAvailable") do(a: Args): self.mailserverReady = true self.requestMissingCommunityInfos() - self.events.on("contactUpdate") do(a: Args): - var evArgs = ContactUpdateArgs(a) - self.updateContacts(evArgs.contacts) - proc statusUpdates*(self: ChatModel) = let statusUpdates = status_chat.statusUpdates() self.events.emit("messagesLoaded", MsgsLoadedArgs(statusUpdates: statusUpdates)) @@ -369,7 +366,7 @@ proc sendSticker*(self: ChatModel, chatId: string, replyTo: string, sticker: Sti var response = status_chat.sendStickerMessage(chatId, replyTo, sticker) self.events.emit("stickerSent", StickerArgs(sticker: sticker, save: true)) var (chats, messages) = self.parseChatResponse(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats)) self.events.emit("sendingMessage", MessageArgs(id: messages[0].id, channel: messages[0].chatId)) proc addEmojiReaction*(self: ChatModel, chatId: string, messageId: string, emojiId: int) = @@ -385,7 +382,7 @@ proc onMarkMessagesRead(self: ChatModel, response: string, chatId: string): Json if self.channels.hasKey(chatId): self.channels[chatId].unviewedMessagesCount = 0 self.channels[chatId].mentionsCount = 0 - self.events.emit("channelUpdate", ChatUpdateArgs(messages: @[], chats: @[self.channels[chatId]], contacts: @[])) + self.events.emit("channelUpdate", ChatUpdateArgs(messages: @[], chats: @[self.channels[chatId]])) proc onAsyncMarkMessagesRead*(self: ChatModel, response: string) = let parsedResponse = parseJson(response) @@ -408,8 +405,9 @@ proc renameGroup*(self: ChatModel, chatId: string, newName: string) = self.emitUpdate(response) proc getUserName*(self: ChatModel, id: string, defaultUserName: string):string = - if(self.contacts.hasKey(id)): - return userNameOrAlias(self.contacts[id]) + let contacts = self.getContacts() + if(contacts.hasKey(id)): + return userNameOrAlias(contacts[id]) else: return defaultUserName @@ -418,7 +416,7 @@ proc processGroupChatCreation*(self: ChatModel, result: string) = var (chats, messages) = formatChatUpdate(response) let chat = chats[0] self.channels[chat.id] = chat - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats)) self.events.emit("activeChannelChanged", ChatIdArg(chatId: chat.id)) proc createGroup*(self: ChatModel, groupName: string, pubKeys: seq[string]) = @@ -446,11 +444,11 @@ proc resendMessage*(self: ChatModel, messageId: string) = proc muteChat*(self: ChatModel, chat: Chat) = discard status_chat.muteChat(chat.id) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat], contacts: @[])) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat])) proc unmuteChat*(self: ChatModel, chat: Chat) = discard status_chat.unmuteChat(chat.id) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat], contacts: @[])) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat])) proc processUpdateForTransaction*(self: ChatModel, messageId: string, response: string) = var (chats, messages) = self.processMessageUpdateAfterSend(response) @@ -741,8 +739,9 @@ proc userNameOrAlias*(self: ChatModel, pubKey: string, ## Returns ens name or alias, in case if prettyForm is true and ens name ## ends with ".stateofus.eth" that part will be removed. var alias: string - if self.contacts.hasKey(pubKey): - alias = ens.userNameOrAlias(self.contacts[pubKey]) + let contacts = self.getContacts() + if contacts.hasKey(pubKey): + alias = ens.userNameOrAlias(contacts[pubKey]) else: alias = generateAlias(pubKey) @@ -755,9 +754,9 @@ proc chatName*(self: ChatModel, chatItem: Chat): string = if (not chatItem.chatType.isOneToOne): return chatItem.name - if (self.contacts.hasKey(chatItem.id) and - self.contacts[chatItem.id].hasNickname()): - return self.contacts[chatItem.id].localNickname + let contacts = self.getContacts() + if (contacts.hasKey(chatItem.id) and contacts[chatItem.id].hasNickname()): + return contacts[chatItem.id].localNickname if chatItem.ensName != "": return "@" & userName(chatItem.ensName).userName(true) diff --git a/status/contacts.nim b/status/contacts.nim index aaacb2b..59978e2 100644 --- a/status/contacts.nim +++ b/status/contacts.nim @@ -1,4 +1,4 @@ -import json, sequtils, sugar, chronicles +import json, chronicles import statusgo_backend/contacts as status_contacts import statusgo_backend/accounts as status_accounts import statusgo_backend/chat as status_chat @@ -19,6 +19,13 @@ proc newContactModel*(events: EventEmitter): ContactModel = result = ContactModel() result.events = events +proc saveContact(self: ContactModel, contact: Profile): string = + var thumbnail = "" + if contact.identityImage != nil: + thumbnail = contact.identityImage.thumbnail + + return status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, thumbnail, contact.systemTags, contact.localNickname) + proc getContactByID*(self: ContactModel, id: string): Profile = let response = status_contacts.getContactByID(id) # TODO: change to options @@ -31,24 +38,21 @@ proc getContactByID*(self: ContactModel, id: string): Profile = proc blockContact*(self: ContactModel, id: string): string = var contact = self.getContactByID(id) contact.systemTags.add(contactBlocked) - discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, contact.identityImage.thumbnail, contact.systemTags, contact.localNickname) + discard self.saveContact(contact) self.events.emit("contactBlocked", Args()) proc unblockContact*(self: ContactModel, id: string): string = var contact = self.getContactByID(id) contact.systemTags.delete(contact.systemTags.find(contactBlocked)) - discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, contact.identityImage.thumbnail, contact.systemTags, contact.localNickname) + discard self.saveContact(contact) self.events.emit("contactUnblocked", Args()) -proc getAllContacts*(): seq[Profile] = - result = map(status_contacts.getContacts().getElems(), proc(x: JsonNode): Profile = x.toProfileModel()) +proc getContacts*(self: ContactModel, useCache: bool = true): seq[Profile] = + let (contacts, usedCache) = status_contacts.getContacts(useCache) + if not usedCache: + self.events.emit("contactUpdate", ContactUpdateArgs(contacts: contacts)) -proc getAddedContacts*(): seq[Profile] = - result = getAllContacts().filter(c => c.systemTags.contains(contactAdded)) - -proc getContacts*(self: ContactModel): seq[Profile] = - result = getAllContacts() - self.events.emit("contactUpdate", ContactUpdateArgs(contacts: result)) + return contacts proc getOrCreateContact*(self: ContactModel, id: string): Profile = result = self.getContactByID(id) @@ -66,7 +70,7 @@ proc getOrCreateContact*(self: ContactModel, id: string): Profile = systemTags: @[] ) -proc setNickName*(self: ContactModel, id: string, localNickname: string): string = +proc setNickName*(self: ContactModel, id: string, localNickname: string, accountKeyUID: string): string = var contact = self.getOrCreateContact(id) let nickname = if (localNickname == ""): @@ -76,18 +80,16 @@ proc setNickName*(self: ContactModel, id: string, localNickname: string): string else: localNickname - var thumbnail = "" - if contact.identityImage != nil: - thumbnail = contact.identityImage.thumbnail - result = status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, thumbnail, contact.systemTags, nickname) + contact.localNickname = nickname + result = self.saveContact(contact) self.events.emit("contactAdded", Args()) - discard requestContactUpdate(contact.id) + discard sendContactUpdate(contact.id, accountKeyUID) - -proc addContact*(self: ContactModel, id: string): string = +proc addContact*(self: ContactModel, id: string, accountKeyUID: string): string = var contact = self.getOrCreateContact(id) let updating = contact.systemTags.contains(contactAdded) + if not updating: contact.systemTags.add(contactAdded) discard status_chat.createProfileChat(contact.id) @@ -96,13 +98,9 @@ proc addContact*(self: ContactModel, id: string): string = if (index > -1): contact.systemTags.delete(index) - var thumbnail = "" - if contact.identityImage != nil: - thumbnail = contact.identityImage.thumbnail - - result = status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, thumbnail, contact.systemTags, contact.localNickname) + result = self.saveContact(contact) self.events.emit("contactAdded", Args()) - discard requestContactUpdate(contact.id) + discard sendContactUpdate(contact.id, accountKeyUID) if updating: let profile = Profile( @@ -122,11 +120,7 @@ proc removeContact*(self: ContactModel, id: string) = let contact = self.getContactByID(id) contact.systemTags.delete(contact.systemTags.find(contactAdded)) - var thumbnail = "" - if contact.identityImage != nil: - thumbnail = contact.identityImage.thumbnail - - discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, thumbnail, contact.systemTags, contact.localNickname) + discard self.saveContact(contact) self.events.emit("contactRemoved", Args()) proc isAdded*(self: ContactModel, id: string): bool = @@ -143,9 +137,5 @@ proc rejectContactRequest*(self: ContactModel, id: string) = let contact = self.getContactByID(id) contact.systemTags.delete(contact.systemTags.find(contactRequest)) - var thumbnail = "" - if contact.identityImage != nil: - thumbnail = contact.identityImage.thumbnail - - discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, thumbnail, contact.systemTags, contact.localNickname) + discard self.saveContact(contact) self.events.emit("contactRemoved", Args()) diff --git a/status/statusgo_backend/contacts.nim b/status/statusgo_backend/contacts.nim index 4e54ab8..6dfa8f3 100644 --- a/status/statusgo_backend/contacts.nim +++ b/status/statusgo_backend/contacts.nim @@ -1,8 +1,9 @@ -import json, strmisc, atomics -import core, ../utils +import tables, json, strmisc, atomics, sugar, sequtils, json_serialization, chronicles +import ./core, ./settings, ./accounts, ../utils, ../types/[profile, setting] var - contacts {.threadvar.}: JsonNode + contacts {.threadvar.}: seq[Profile] + contactsIndex {.threadvar.}: Table[string, Profile] contactsInited {.threadvar.}: bool dirty: Atomic[bool] @@ -10,20 +11,35 @@ proc getContactByID*(id: string): string = result = callPrivateRPC("getContactByID".prefix, %* [id]) dirty.store(true) -proc getContacts*(): JsonNode = - let cacheIsDirty = (not contactsInited) or dirty.load +proc getContacts*(useCache: bool = true): (seq[Profile], bool) = + let cacheIsDirty = (not useCache) or (not contactsInited) or dirty.load if not cacheIsDirty: - result = contacts + return (contacts, true) + + let payload = %* [] + let response = callPrivateRPC("contacts".prefix, payload).parseJson + dirty.store(false) + contactsIndex = initTable[string, Profile]() + contactsInited = true + + if response["result"].kind == JNull: + contacts = @[] + return (contacts, false) + + contacts = map(response["result"].getElems(), proc(x: JsonNode): Profile = x.toProfileModel()) + for contact in contacts: + contactsIndex[contact.id] = contact + + return (contacts, false) + +proc getContactsIndex*(): (Table[string, Profile], bool)= + let cacheIsDirty = (not contactsInited) or dirty.load + + if not cacheIsDirty: + return (contactsIndex, true) else: - let payload = %* [] - let response = callPrivateRPC("contacts".prefix, payload).parseJson - if response["result"].kind == JNull: - result = %* [] - else: - result = response["result"] - dirty.store(false) - contacts = result - contactsInited = true + discard getContacts() + return (contactsIndex, false) proc saveContact*(id: string, ensVerified: bool, ensName: string, alias: string, identicon: string, thumbnail: string, systemTags: seq[string], localNickname: string): string = let payload = %* [{ @@ -40,6 +56,15 @@ proc saveContact*(id: string, ensVerified: bool, ensName: string, alias: string, result = callPrivateRPC("saveContact".prefix, payload) dirty.store(true) -proc requestContactUpdate*(publicKey: string): string = - result = callPrivateRPC("sendContactUpdate".prefix, %* [publicKey, "", ""]) +proc sendContactUpdate*(publicKey: string, accountKeyUID: string) : string = + let preferredUsername = getSetting[string](Setting.PreferredUsername, "") + let usernames = getSetting[seq[string]](Setting.Usernames, @[]) + var ensName = "" + if len(preferredUsername) > 0: + ensName = preferredUsername + elif len(usernames) >= 1: + ensName = usernames[0] + + let identityImage = getIdentityImage(accountKeyUID) + result = callPrivateRPC("sendContactUpdate".prefix, %* [publicKey, ensName, identityImage.thumbnail]) dirty.store(true) diff --git a/status/types/profile.nim b/status/types/profile.nim index 540bf37..0d8bfd0 100644 --- a/status/types/profile.nim +++ b/status/types/profile.nim @@ -16,7 +16,7 @@ type Profile* = ref object systemTags*: seq[string] proc `$`*(self: Profile): string = - return fmt"Profile(id:{self.id}, username:{self.username})" + return fmt"Profile(id:{self.id}, username:{self.username}, systemTags: {self.systemTags}, ensName: {self.ensName})" proc toProfileModel*(profile: JsonNode): Profile = var systemTags: seq[string] = @[]