diff --git a/src/status/chat.nim b/src/status/chat.nim index d2616cd504..53a02dbd83 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -1,11 +1,9 @@ -import eventemitter, json -import sequtils +import eventemitter, json, strutils, sequtils, tables import libstatus/chat as status_chat import chronicles import profile/profile import chat/[chat, message] import ../signals/messages -import tables import ens type @@ -33,15 +31,15 @@ type events*: EventEmitter contacts*: Table[string, Profile] channels*: Table[string, Chat] - filters*: Table[string, string] msgCursor*: Table[string, string] +include chat/utils + proc newChatModel*(events: EventEmitter): ChatModel = result = ChatModel() result.events = events result.contacts = initTable[string, Profile]() result.channels = initTable[string, Chat]() - result.filters = initTable[string, string]() result.msgCursor = initTable[string, string]() proc delete*(self: ChatModel) = @@ -73,7 +71,6 @@ proc join*(self: ChatModel, chatId: string, chatType: ChatType) = for topicObj in parsedResult: if ($topicObj["chatId"].getStr == chatId): topics.add($topicObj["topic"].getStr) - if(not self.filters.hasKey(chatId)): self.filters[chatId] = topicObj["filterId"].getStr if (topics.len == 0): warn "No topics found for chats. Cannot load past messages" @@ -103,40 +100,22 @@ proc init*(self: ChatModel) = let parsedResult = parseJson(filterResult)["result"] for topicObj in parsedResult: topics.add($topicObj["topic"].getStr) - self.filters[$topicObj["chatId"].getStr] = topicObj["filterId"].getStr if (topics.len == 0): warn "No topics found for chats. Cannot load past messages" else: self.events.emit("mailserverTopics", TopicArgs(topics: topics)); -proc processChatUpdate(self: ChatModel,response: JsonNode): (seq[Chat], seq[Message]) = - var chats: seq[Chat] = @[] - var messages: seq[Message] = @[] - if response["result"]{"chats"} != nil: - for jsonMsg in response["result"]["messages"]: - messages.add(jsonMsg.toMessage) - if response["result"]{"chats"} != nil: - for jsonChat in response["result"]["chats"]: - let chat = jsonChat.toChat - self.channels[chat.id] = chat - chats.add(chat) - result = (chats, messages) - - proc leave*(self: ChatModel, chatId: string) = + self.removeChatFilters(chatId) + if self.channels[chatId].chatType == ChatType.PrivateGroupChat: let leaveGroupResponse = status_chat.leaveGroupChat(chatId) - var (chats, messages) = self.processChatUpdate(parseJson(leaveGroupResponse)) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.emitUpdate(leaveGroupResponse) - # We still want to be able to receive messages unless we block the 1:1 sender - if self.filters.hasKey(chatId) and self.channels[chatId].chatType == ChatType.Public: - status_chat.removeFilters(chatId, self.filters[chatId]) - status_chat.deactivateChat(self.channels[chatId]) + # TODO: REMOVE MAILSERVER TOPIC - self.filters.del(chatId) self.channels.del(chatId) discard status_chat.clearChatHistory(chatId) self.events.emit("channelLeft", ChatIdArg(chatId: chatId)) @@ -151,21 +130,9 @@ proc clearHistory*(self: ChatModel, chatId: string) = proc setActiveChannel*(self: ChatModel, chatId: string) = self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId)) -proc formatChatUpdate(response: JsonNode): (seq[Chat], seq[Message]) = - var chats: seq[Chat] = @[] - var messages: seq[Message] = @[] - if response["result"]{"chats"} != nil: - for jsonMsg in response["result"]["messages"]: - messages.add(jsonMsg.toMessage) - if response["result"]{"chats"} != nil: - for jsonChat in response["result"]["chats"]: - chats.add(jsonChat.toChat) - result = (chats, messages) - proc sendMessage*(self: ChatModel, chatId: string, msg: string): string = var sentMessage = status_chat.sendChatMessage(chatId, msg) - var (chats, messages) = self.processChatUpdate(parseJson(sentMessage)) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.emitUpdate(sentMessage) sentMessage proc chatMessages*(self: ChatModel, chatId: string, initialLoad:bool = true) = @@ -185,14 +152,12 @@ proc markAllChannelMessagesRead*(self: ChatModel, chatId: string): JsonNode = result = parseJson(response) proc confirmJoiningGroup*(self: ChatModel, chatId: string) = - var response = parseJson(status_chat.confirmJoiningGroup(chatId)) - var (chats, messages) = self.processChatUpdate(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + var response = status_chat.confirmJoiningGroup(chatId) + self.emitUpdate(response) proc renameGroup*(self: ChatModel, chatId: string, newName: string) = - var response = parseJson(status_chat.renameGroup(chatId, newName)) - var (chats, messages) = formatChatUpdate(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + var response = status_chat.renameGroup(chatId, newName) + self.emitUpdate(response) proc getUserName*(self: ChatModel, id: string, defaultUserName: string):string = if(self.contacts.hasKey(id)): diff --git a/src/status/chat/utils.nim b/src/status/chat/utils.nim new file mode 100644 index 0000000000..cf06ab1da5 --- /dev/null +++ b/src/status/chat/utils.nim @@ -0,0 +1,81 @@ +proc formatChatUpdate(response: JsonNode): (seq[Chat], seq[Message]) = + var chats: seq[Chat] = @[] + var messages: seq[Message] = @[] + if response["result"]{"chats"} != nil: + for jsonMsg in response["result"]["messages"]: + messages.add(jsonMsg.toMessage) + if response["result"]{"chats"} != nil: + for jsonChat in response["result"]["chats"]: + chats.add(jsonChat.toChat) + result = (chats, messages) + +proc processChatUpdate(self: ChatModel, response: JsonNode): (seq[Chat], seq[Message]) = + var chats: seq[Chat] = @[] + var messages: seq[Message] = @[] + if response["result"]{"chats"} != nil: + for jsonMsg in response["result"]["messages"]: + messages.add(jsonMsg.toMessage) + if response["result"]{"chats"} != nil: + for jsonChat in response["result"]["chats"]: + let chat = jsonChat.toChat + self.channels[chat.id] = chat + chats.add(chat) + result = (chats, messages) + +proc emitUpdate(self: ChatModel, response: string) = + var (chats, messages) = self.processChatUpdate(parseJson(response)) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + +proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) + +proc removeChatFilters(self: ChatModel, chatId: string) = + # TODO: this code should be handled by status-go / stimbus instead of the client + # Clients should not have to care about filters. For more info about filters: + # https://github.com/status-im/specs/blob/master/docs/stable/3-whisper-usage.md#keys-management + let filters = parseJson(status_chat.loadFilters(@[]))["result"] + + case self.channels[chatId].chatType + of ChatType.Public: + for filter in filters: + if filter["chatId"].getStr == chatId: + status_chat.removeFilters(chatId, filter["filterId"].getStr) + of ChatType.OneToOne: + # Check if user does not belong to any active chat group + var inGroup = false + for channel in self.channels.values: + if channel.isActive and channel.id != chatId and channel.chatType == ChatType.PrivateGroupChat: + inGroup = true + break + if not inGroup: self.removeFiltersByChatId(chatId, filters) + of ChatType.PrivateGroupChat: + for member in self.channels[chatId].members: + # Check that any of the members are not in other active group chats, or that you don’t have a one-to-one open. + var hasConversation = false + for channel in self.channels.values: + if (channel.isActive and channel.chatType == ChatType.OneToOne and channel.id == member.id) or + (channel.isActive and channel.id != chatId and channel.chatType == ChatType.PrivateGroupChat and channel.isMember(member.id)): + hasConversation = true + break + if not hasConversation: self.removeFiltersByChatId(member.id, filters) + else: + error "Unknown chat type removed", chatId + +proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) = + var partitionedTopic = "" + for filter in filters: + # Contact code filter should be removed + if filter["identity"].getStr == chatId and filter["chatId"].getStr.endsWith("-contact-code"): + status_chat.removeFilters(chatId, filter["filterId"].getStr) + + # Remove partitioned topic if no other user in an active group chat or one-to-one is from the + # same partitioned topic + if filter["identity"].getStr == chatId and filter["chatId"].getStr.startsWith("contact-discovery-"): + partitionedTopic = filter["topic"].getStr + var samePartitionedTopic = false + for f in filters.filterIt(it["topic"].getStr == partitionedTopic and it["filterId"].getStr != filter["filterId"].getStr): + let fIdentity = f["identity"].getStr; + if self.channels.hasKey(fIdentity) and self.channels[fIdentity].isActive: + samePartitionedTopic = true + break + if not samePartitionedTopic: + status_chat.removeFilters(chatId, filter["filterId"].getStr) diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index f3e6c5c47b..a02dcbb6f9 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -28,6 +28,8 @@ proc removeFilters*(chatId: string, filterId: string) = ]) proc saveChat*(chatId: string, oneToOne: bool = false, active: bool = true, color: string) = + # TODO: ideally status-go/stimbus should handle some of these fields instead of having the client + # send them: lastMessage, unviewedMEssagesCount, timestamp, lastClockValue, name? discard callPrivateRPC("saveChat".prefix, %* [ { "lastClockValue": 0, # TODO: