434 lines
16 KiB
Nim
434 lines
16 KiB
Nim
import NimQml, Tables, json, sequtils, strformat, chronicles, os
|
|
|
|
import ./dto/chat as chat_dto
|
|
import ../message/dto/message as message_dto
|
|
import ../activity_center/dto/notification as notification_dto
|
|
import ../contacts/service as contact_service
|
|
import ../../../backend/chat as status_chat
|
|
import ../../../backend/chatCommands as status_chat_commands
|
|
import ../../../app/global/global_singleton
|
|
import ../../../app/core/eventemitter
|
|
import ../../../constants
|
|
|
|
from ../../common/account_constants import ZERO_ADDRESS
|
|
|
|
export chat_dto
|
|
|
|
|
|
logScope:
|
|
topics = "chat-service"
|
|
|
|
include ../../common/json_utils
|
|
|
|
type
|
|
# TODO remove New when refactored
|
|
ChatUpdateArgsNew* = ref object of Args
|
|
chats*: seq[ChatDto]
|
|
messages*: seq[MessageDto]
|
|
# TODO refactor that part
|
|
# pinnedMessages*: seq[MessageDto]
|
|
# emojiReactions*: seq[Reaction]
|
|
# communities*: seq[Community]
|
|
# communityMembershipRequests*: seq[CommunityMembershipRequest]
|
|
activityCenterNotifications*: seq[ActivityCenterNotificationDto]
|
|
# statusUpdates*: seq[StatusUpdate]
|
|
# deletedMessages*: seq[RemovedMessage]
|
|
|
|
ChatArgs* = ref object of Args
|
|
chatId*: string
|
|
|
|
MessageSendingSuccess* = ref object of Args
|
|
chat*: ChatDto
|
|
message*: MessageDto
|
|
|
|
MessageArgs* = ref object of Args
|
|
id*: string
|
|
channel*: string
|
|
|
|
ChatRenameArgs* = ref object of Args
|
|
id*: string
|
|
newName*: string
|
|
|
|
ChatMembersAddedArgs* = ref object of Args
|
|
chatId*: string
|
|
ids*: seq[string]
|
|
|
|
ChatMemberRemovedArgs* = ref object of Args
|
|
chatId*: string
|
|
id*: string
|
|
|
|
|
|
# Signals which may be emitted by this service:
|
|
const SIGNAL_CHAT_UPDATE* = "chatUpdate_new"
|
|
const SIGNAL_CHAT_LEFT* = "channelLeft_new"
|
|
const SIGNAL_SENDING_FAILED* = "messageSendingFailed"
|
|
const SIGNAL_SENDING_SUCCESS* = "messageSendingSuccess"
|
|
const SIGNAL_MESSAGE_DELETED* = "messageDeleted"
|
|
const SIGNAL_CHAT_MUTED* = "chatMuted"
|
|
const SIGNAL_CHAT_UNMUTED* = "chatUnmuted"
|
|
const SIGNAL_CHAT_HISTORY_CLEARED* = "chatHistoryCleared"
|
|
const SIGNAL_CHAT_RENAMED* = "chatRenamed"
|
|
const SIGNAL_CHAT_MEMBERS_ADDED* = "chatMemberAdded"
|
|
const SIGNAL_CHAT_MEMBER_REMOVED* = "chatMemberRemoved"
|
|
|
|
QtObject:
|
|
type Service* = ref object of QObject
|
|
events: EventEmitter
|
|
chats: Table[string, ChatDto] # [chat_id, ChatDto]
|
|
contactService: contact_service.Service
|
|
|
|
proc delete*(self: Service) =
|
|
discard
|
|
|
|
proc newService*(events: EventEmitter, contactService: contact_service.Service): Service =
|
|
new(result, delete)
|
|
result.events = events
|
|
result.contactService = contactService
|
|
result.chats = initTable[string, ChatDto]()
|
|
|
|
proc init*(self: Service) =
|
|
try:
|
|
let response = status_chat.getChats()
|
|
|
|
let chats = map(response.result.getElems(), proc(x: JsonNode): ChatDto = x.toChatDto())
|
|
|
|
for chat in chats:
|
|
if chat.active and chat.chatType != chat_dto.ChatType.Unknown:
|
|
self.chats[chat.id] = chat
|
|
|
|
except Exception as e:
|
|
let errDesription = e.msg
|
|
error "error: ", errDesription
|
|
return
|
|
|
|
proc hasChannel*(self: Service, chatId: string): bool =
|
|
self.chats.hasKey(chatId)
|
|
|
|
proc updateOrAddChat*(self: Service, chat: ChatDto) =
|
|
self.chats[chat.id] = chat
|
|
|
|
proc parseChatResponse*(self: Service, response: RpcResponse[JsonNode]): (seq[ChatDto], seq[MessageDto]) =
|
|
var chats: seq[ChatDto] = @[]
|
|
var messages: seq[MessageDto] = @[]
|
|
if response.result{"messages"} != nil:
|
|
for jsonMsg in response.result["messages"]:
|
|
messages.add(jsonMsg.toMessageDto)
|
|
if response.result{"chats"} != nil:
|
|
for jsonChat in response.result["chats"]:
|
|
let chat = chat_dto.toChatDto(jsonChat)
|
|
# TODO add the channel back to `chat` when it is refactored
|
|
self.updateOrAddChat(chat)
|
|
chats.add(chat)
|
|
result = (chats, messages)
|
|
|
|
proc processMessageUpdateAfterSend*(self: Service, response: RpcResponse[JsonNode]): (seq[ChatDto], seq[MessageDto]) =
|
|
result = self.parseChatResponse(response)
|
|
var (chats, messages) = result
|
|
if chats.len == 0 or messages.len == 0:
|
|
error "no chats or messages in the parsed response"
|
|
return
|
|
|
|
# The reason why we are sending all the messages with responseTo filled in is because
|
|
# the reposnse from status_go doesnt necessarily contain the last reply on the 0th position.
|
|
var isaReply = false
|
|
var msg = messages[0]
|
|
for m in messages:
|
|
if(m.responseTo.len > 0):
|
|
isaReply = true
|
|
msg = m
|
|
self.events.emit(SIGNAL_SENDING_SUCCESS, MessageSendingSuccess(message: msg, chat: chats[0]))
|
|
|
|
if not isaReply:
|
|
self.events.emit(SIGNAL_SENDING_SUCCESS, MessageSendingSuccess(message: msg, chat: chats[0]))
|
|
|
|
proc processUpdateForTransaction*(self: Service, messageId: string, response: RpcResponse[JsonNode]) =
|
|
var (chats, messages) = self.processMessageUpdateAfterSend(response)
|
|
self.events.emit(SIGNAL_MESSAGE_DELETED, MessageArgs(id: messageId, channel: chats[0].id))
|
|
|
|
proc emitUpdate(self: Service, response: RpcResponse[JsonNode]) =
|
|
var (chats, messages) = self.parseChatResponse(response)
|
|
self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgsNew(messages: messages, chats: chats))
|
|
|
|
proc getAllChats*(self: Service): seq[ChatDto] =
|
|
return toSeq(self.chats.values)
|
|
|
|
proc getChatsOfChatTypes*(self: Service, types: seq[chat_dto.ChatType]): seq[ChatDto] =
|
|
return self.getAllChats().filterIt(it.chatType in types)
|
|
|
|
proc getChatById*(self: Service, chatId: string, showWarning: bool = true): ChatDto =
|
|
if(not self.chats.contains(chatId)):
|
|
if (showWarning):
|
|
warn "trying to get chat data for an unexisting chat id", chatId
|
|
return
|
|
|
|
return self.chats[chatId]
|
|
|
|
proc getOneToOneChatNameAndImage*(self: Service, chatId: string):
|
|
tuple[name: string, image: string, isIdenticon: bool] =
|
|
return self.contactService.getContactNameAndImage(chatId)
|
|
|
|
proc createChatFromResponse(self: Service, response: RpcResponse[JsonNode]): tuple[chatDto: ChatDto, success: bool] =
|
|
var jsonArr: JsonNode
|
|
if (not response.result.getProp("chats", jsonArr)):
|
|
error "error: response of creating chat doesn't contain created chats"
|
|
result.success = false
|
|
return
|
|
|
|
let chats = map(jsonArr.getElems(), proc(x: JsonNode): ChatDto = x.toChatDto())
|
|
# created chat is returned as the first elemnt of json array (it's up to `status-go`)
|
|
if(chats.len == 0):
|
|
error "error: unknown error occured creating chat"
|
|
result.success = false
|
|
return
|
|
|
|
result.chatDto = chats[0]
|
|
self.updateOrAddChat(result.chatDto)
|
|
result.success = true
|
|
|
|
proc createPublicChat*(self: Service, chatId: string): tuple[chatDto: ChatDto, success: bool] =
|
|
try:
|
|
let response = status_chat.createPublicChat(chatId)
|
|
result = self.createChatFromResponse(response)
|
|
except Exception as e:
|
|
let errDesription = e.msg
|
|
error "error: ", errDesription
|
|
return
|
|
|
|
proc createOneToOneChat*(self: Service, chatId: string, ensName: string): tuple[chatDto: ChatDto, success: bool] =
|
|
try:
|
|
if self.hasChannel(chatId):
|
|
# We want to show the chat to the user and for that we activate the chat
|
|
discard status_chat.saveChat(
|
|
chatId,
|
|
chat_dto.ChatType.OneToOne.int,
|
|
color=self.chats[chatId].color,
|
|
ensName=ensName)
|
|
result.success = true
|
|
result.chatDto = self.chats[chatId]
|
|
return
|
|
|
|
let response = status_chat.createOneToOneChat(chatId)
|
|
result = self.createChatFromResponse(response)
|
|
except Exception as e:
|
|
let errDesription = e.msg
|
|
error "error: ", errDesription
|
|
return
|
|
|
|
proc leaveChat*(self: Service, chatId: string) =
|
|
try:
|
|
if self.chats.len == 0:
|
|
return
|
|
if(not self.chats.contains(chatId)):
|
|
error "trying to leave chat for an unexisting chat id", chatId
|
|
return
|
|
|
|
let chat = self.chats[chatId]
|
|
if chat.chatType == chat_dto.ChatType.PrivateGroupChat:
|
|
let leaveGroupResponse = status_chat.leaveGroupChat(chatId)
|
|
self.emitUpdate(leaveGroupResponse)
|
|
|
|
discard status_chat.deactivateChat(chatId)
|
|
|
|
self.chats.del(chatId)
|
|
discard status_chat.clearChatHistory(chatId)
|
|
self.events.emit(SIGNAL_CHAT_LEFT, ChatArgs(chatId: chatId))
|
|
except Exception as e:
|
|
error "Error deleting channel", chatId, msg = e.msg
|
|
return
|
|
|
|
proc sendImages*(self: Service, chatId: string, imagePathsJson: string): string =
|
|
result = ""
|
|
try:
|
|
var images = Json.decode(imagePathsJson, seq[string])
|
|
|
|
for imagePath in images.mitems:
|
|
var image = singletonInstance.utils.formatImagePath(imagePath)
|
|
imagePath = image_resizer(image, 2000, TMPDIR)
|
|
|
|
let response = status_chat.sendImages(chatId, images)
|
|
|
|
for imagePath in images.items:
|
|
removeFile(imagePath)
|
|
|
|
discard self.processMessageUpdateAfterSend(response)
|
|
except Exception as e:
|
|
error "Error sending images", msg = e.msg
|
|
result = fmt"Error sending images: {e.msg}"
|
|
|
|
proc sendChatMessage*(
|
|
self: Service,
|
|
chatId: string,
|
|
msg: string,
|
|
replyTo: string,
|
|
contentType: int,
|
|
preferredUsername: string = "",
|
|
communityId: string = "") =
|
|
try:
|
|
let response = status_chat.sendChatMessage(
|
|
chatId,
|
|
msg,
|
|
replyTo,
|
|
contentType,
|
|
preferredUsername,
|
|
communityId) # Only send a community ID for the community invites
|
|
|
|
let (chats, messages) = self.processMessageUpdateAfterSend(response)
|
|
if chats.len == 0 or messages.len == 0:
|
|
self.events.emit(SIGNAL_SENDING_FAILED, ChatArgs(chatId: chatId))
|
|
except Exception as e:
|
|
error "Error sending message", msg = e.msg
|
|
|
|
proc requestAddressForTransaction*(self: Service, chatId: string, fromAddress: string, amount: string, tokenAddress: string) =
|
|
try:
|
|
let address = if (tokenAddress == ZERO_ADDRESS): "" else: tokenAddress
|
|
let response = status_chat_commands.requestAddressForTransaction(chatId, fromAddress, amount, address)
|
|
discard self.processMessageUpdateAfterSend(response)
|
|
except Exception as e:
|
|
error "Error requesting address for transaction", msg = e.msg
|
|
|
|
proc requestTransaction*(self: Service, chatId: string, fromAddress: string, amount: string, tokenAddress: string) =
|
|
try:
|
|
let address = if (tokenAddress == ZERO_ADDRESS): "" else: tokenAddress
|
|
let response = status_chat_commands.requestTransaction(chatId, fromAddress, amount, address)
|
|
discard self.processMessageUpdateAfterSend(response)
|
|
except Exception as e:
|
|
error "Error requesting transaction", msg = e.msg
|
|
|
|
proc declineRequestTransaction*(self: Service, messageId: string) =
|
|
try:
|
|
let response = status_chat_commands.declineRequestTransaction(messageId)
|
|
self.processUpdateForTransaction(messageId, response)
|
|
except Exception as e:
|
|
error "Error requesting transaction", msg = e.msg
|
|
|
|
proc declineRequestAddressForTransaction*(self: Service, messageId: string) =
|
|
try:
|
|
let response = status_chat_commands.declineRequestAddressForTransaction(messageId)
|
|
self.processUpdateForTransaction(messageId, response)
|
|
except Exception as e:
|
|
error "Error requesting transaction", msg = e.msg
|
|
|
|
proc acceptRequestAddressForTransaction*(self: Service, messageId: string, address: string) =
|
|
try:
|
|
let response = status_chat_commands.acceptRequestAddressForTransaction(messageId, address)
|
|
self.processUpdateForTransaction(messageId, response)
|
|
except Exception as e:
|
|
error "Error requesting transaction", msg = e.msg
|
|
|
|
proc acceptRequestTransaction*(self: Service, transactionHash: string, messageId: string, signature: string) =
|
|
try:
|
|
let response = status_chat_commands.acceptRequestTransaction(transactionHash, messageId, signature)
|
|
discard self.processMessageUpdateAfterSend(response)
|
|
except Exception as e:
|
|
error "Error requesting transaction", msg = e.msg
|
|
|
|
proc muteChat*(self: Service, chatId: string) =
|
|
try:
|
|
if(chatId.len == 0):
|
|
error "error trying to mute chat with an empty id"
|
|
return
|
|
|
|
let response = status_chat.muteChat(chatId)
|
|
if(not response.error.isNil):
|
|
let msg = response.error.message & " chatId=" & chatId
|
|
error "error while mute chat ", msg
|
|
return
|
|
|
|
self.events.emit(SIGNAL_CHAT_MUTED, ChatArgs(chatId: chatId))
|
|
except Exception as e:
|
|
let errDesription = e.msg
|
|
error "error: ", errDesription
|
|
return
|
|
|
|
proc unmuteChat*(self: Service, chatId: string) =
|
|
try:
|
|
if(chatId.len == 0):
|
|
error "error trying to unmute chat with an empty id"
|
|
return
|
|
|
|
let response = status_chat.unmuteChat(chatId)
|
|
if(not response.error.isNil):
|
|
let msg = response.error.message & " chatId=" & chatId
|
|
error "error while unmute chat ", msg
|
|
return
|
|
|
|
self.events.emit(SIGNAL_CHAT_UNMUTED, ChatArgs(chatId: chatId))
|
|
except Exception as e:
|
|
let errDesription = e.msg
|
|
error "error: ", errDesription
|
|
return
|
|
|
|
method clearChatHistory*(self: Service, chatId: string) =
|
|
try:
|
|
let response = status_chat.deleteMessagesByChatId(chatId)
|
|
if(not response.error.isNil):
|
|
let msg = response.error.message & " chatId=" & chatId
|
|
error "error while clearing chat history ", msg
|
|
return
|
|
|
|
self.events.emit(SIGNAL_CHAT_HISTORY_CLEARED, ChatArgs(chatId: chatId))
|
|
except Exception as e:
|
|
let errDesription = e.msg
|
|
error "error: ", errDesription
|
|
return
|
|
|
|
method addGroupMembers*(self: Service, chatId: string, pubKeys: seq[string]) =
|
|
try:
|
|
let response = status_chat.addGroupMembers(chatId, pubKeys)
|
|
if (response.error.isNil):
|
|
self.events.emit(SIGNAL_CHAT_MEMBERS_ADDED, ChatMembersAddedArgs(chatId: chatId, ids: pubKeys))
|
|
except Exception as e:
|
|
error "error while adding group members: ", msg = e.msg
|
|
|
|
method removeMemberFromGroupChat*(self: Service, chatId: string, pubKey: string) =
|
|
try:
|
|
let response = status_chat.removeMembersFromGroupChat(chatId, pubKey)
|
|
if (response.error.isNil):
|
|
self.events.emit(SIGNAL_CHAT_MEMBER_REMOVED, ChatMemberRemovedArgs(chatId: chatId, id: pubkey))
|
|
except Exception as e:
|
|
error "error while removing member from group: ", msg = e.msg
|
|
|
|
|
|
method renameGroupChat*(self: Service, chatId: string, newName: string) =
|
|
try:
|
|
let response = status_chat.renameGroupChat(chatId, newName)
|
|
if (not response.error.isNil):
|
|
let msg = response.error.message & " chatId=" & chatId
|
|
error "error while renaming group chat", msg
|
|
return
|
|
|
|
self.events.emit(SIGNAL_CHAT_RENAMED, ChatRenameArgs(id: chatId, newName: newName))
|
|
except Exception as e:
|
|
error "error while renaming group chat: ", msg = e.msg
|
|
|
|
|
|
method makeAdmin*(self: Service, chatId: string, pubKey: string) =
|
|
try:
|
|
let response = status_chat.makeAdmin(chatId, pubKey)
|
|
self.emitUpdate(response)
|
|
except Exception as e:
|
|
error "error while making user admin: ", msg = e.msg
|
|
|
|
|
|
method confirmJoiningGroup*(self: Service, chatId: string) =
|
|
try:
|
|
let response = status_chat.confirmJoiningGroup(chatId)
|
|
self.emitUpdate(response)
|
|
except Exception as e:
|
|
error "error while confirmation joining to group: ", msg = e.msg
|
|
|
|
method createGroupChatFromInvitation*(self: Service, groupName: string, chatId: string, adminPK: string): tuple[chatDto: ChatDto, success: bool] =
|
|
try:
|
|
let response = status_chat.createGroupChatFromInvitation(groupName, chatId, adminPK)
|
|
result = self.createChatFromResponse(response)
|
|
except Exception as e:
|
|
error "error while creating group from invitation: ", msg = e.msg
|
|
|
|
method createGroupChat*(self: Service, groupName: string, pubKeys: seq[string]): tuple[chatDto: ChatDto, success: bool] =
|
|
try:
|
|
let response = status_chat.createGroupChat(groupName, pubKeys)
|
|
result = self.createChatFromResponse(response)
|
|
except Exception as e:
|
|
error "error while creating group chat", msg = e.msg
|
|
|