status-lib/status/statusgo_backend/chat.nim

588 lines
22 KiB
Nim

import json, times, strutils, sequtils, chronicles, json_serialization, algorithm, strformat, sugar
import core, ../utils
import ../types/[chat, message, community, activity_center_notification,
status_update, rpc_response, setting, sticker]
import ./settings as status_settings
proc loadFilters*(filters: seq[JsonNode]): string =
result = callPrivateRPC("loadFilters".prefix, %* [filter(filters, proc(x:JsonNode):bool = x.kind != JNull)])
proc removeFilters*(chatId: string, filterId: string) =
discard callPrivateRPC("removeFilters".prefix, %* [
[{ "ChatID": chatId, "FilterID": filterId }]
])
proc saveChat*(chatId: string, chatType: ChatType, active: bool = true, color: string = "#000000", ensName: string = "", profile: string = "", joined: int64 = 0) =
# 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:
"color": color,
"name": (if ensName != "": ensName else: chatId),
"lastMessage": nil, # TODO:
"active": active,
"profile": profile,
"id": chatId,
"unviewedMessagesCount": 0, # TODO:
"chatType": chatType.int,
"timestamp": 1588940692659, # TODO:
"joined": joined
}
])
proc createPublicChat*(chatId: string):string =
callPrivateRPC("createPublicChat".prefix, %* [{"ID": chatId}])
proc createOneToOneChat*(chatId: string):string =
callPrivateRPC("createOneToOneChat".prefix, %* [{"ID": chatId}])
proc deactivateChat*(chat: Chat):string =
chat.isActive = false
callPrivateRPC("deactivateChat".prefix, %* [{ "ID": chat.id }])
proc createProfileChat*(pubKey: string):string =
callPrivateRPC("createProfileChat".prefix, %* [{ "ID": pubKey }])
proc loadChats*(): seq[Chat] =
result = @[]
let jsonResponse = parseJson($callPrivateRPC("chats".prefix))
if jsonResponse["result"].kind != JNull:
for jsonChat in jsonResponse{"result"}:
let chat = jsonChat.toChat
if chat.isActive and chat.chatType != ChatType.Unknown:
result.add(chat)
proc statusUpdates*(): seq[StatusUpdate] =
let rpcResult = callPrivateRPC("statusUpdates".prefix, %* []).parseJson()["result"]
if rpcResult != nil and rpcResult{"statusUpdates"} != nil and rpcResult["statusUpdates"].len != 0:
for jsonStatusUpdate in rpcResult["statusUpdates"]:
result.add(jsonStatusUpdate.toStatusUpdate)
proc fetchChatMessages*(chatId: string, cursorVal: string, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("chatMessages".prefix, %* [chatId, cursorVal, limit])
except RpcException as e:
success = false
result = e.msg
proc editMessage*(messageId: string, msg: string): string =
callPrivateRPC("editMessage".prefix, %* [
{
"id": messageId,
"text": msg
}
])
proc deleteMessageAndSend*(messageId: string): string =
callPrivateRPC("deleteMessageAndSend".prefix, %* [messageId])
proc rpcReactions*(chatId: string, cursorVal: string, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("emojiReactionsByChatID".prefix, %* [chatId, cursorVal, limit])
except RpcException as e:
success = false
result = e.msg
proc addEmojiReaction*(chatId: string, messageId: string, emojiId: int): seq[Reaction] =
let rpcResult = parseJson(callPrivateRPC("sendEmojiReaction".prefix, %* [chatId, messageId, emojiId]))["result"]
var reactions: seq[Reaction] = @[]
if rpcResult != nil and rpcResult["emojiReactions"] != nil and rpcResult["emojiReactions"].len != 0:
for jsonMsg in rpcResult["emojiReactions"]:
reactions.add(jsonMsg.toReaction)
result = reactions
proc removeEmojiReaction*(emojiReactionId: string): seq[Reaction] =
let rpcResult = parseJson(callPrivateRPC("sendEmojiReactionRetraction".prefix, %* [emojiReactionId]))["result"]
var reactions: seq[Reaction] = @[]
if rpcResult != nil and rpcResult["emojiReactions"] != nil and rpcResult["emojiReactions"].len != 0:
for jsonMsg in rpcResult["emojiReactions"]:
reactions.add(jsonMsg.toReaction)
result = reactions
# TODO this probably belongs in another file
proc generateSymKeyFromPassword*(): string =
result = ($parseJson(callPrivateRPC("waku_generateSymKeyFromPassword", %* [
# TODO unhardcode this for non-status mailservers
"status-offline-inbox"
]))["result"]).strip(chars = {'"'})
proc sendChatMessage*(chatId: string, msg: string, replyTo: string, contentType: int, communityId: string = ""): string =
let preferredUsername = getSetting[string](Setting.PreferredUsername, "")
callPrivateRPC("sendChatMessage".prefix, %* [
{
"chatId": chatId,
"text": msg,
"responseTo": replyTo,
"ensName": preferredUsername,
"sticker": nil,
"contentType": contentType,
"communityId": communityId
}
])
proc sendAudioMessage*(chatId: string, audioBase64: string, durationMs: uint64): string =
let preferredUsername = getSetting[string](Setting.PreferredUsername, "")
callPrivateRPC("sendChatMessage".prefix, %* [
{
"chatId": chatId,
"contentType": ContentType.Audio.int,
"base64Audio": audioBase64,
"audioDurationMs": durationMs,
"ensName": preferredUsername,
"text": "Update to latest version to hear this audio!"
}
])
proc sendImageMessage*(chatId: string, image: string): string =
let preferredUsername = getSetting[string](Setting.PreferredUsername, "")
callPrivateRPC("sendChatMessage".prefix, %* [
{
"chatId": chatId,
"contentType": ContentType.Image.int,
"imagePath": image,
"ensName": preferredUsername,
"text": "Update to latest version to see a nice image here!"
}
])
proc sendImageMessages*(chatId: string, images: var seq[string]): string =
let
preferredUsername = getSetting[string](Setting.PreferredUsername, "")
let imagesJson = %* images.map(image => %*
{
"chatId": chatId,
"contentType": ContentType.Image.int,
"imagePath": image,
"ensName": preferredUsername,
"text": "Update to latest version to see a nice image here!"
}
)
callPrivateRPC("sendChatMessages".prefix, %* [imagesJson])
proc sendStickerMessage*(chatId: string, replyTo: string, sticker: Sticker): string =
let preferredUsername = getSetting[string](Setting.PreferredUsername, "")
callPrivateRPC("sendChatMessage".prefix, %* [
{
"chatId": chatId,
"text": "Update to latest version to see a nice sticker here!",
"responseTo": replyTo,
"ensName": preferredUsername,
"sticker": {
"hash": sticker.hash,
"pack": sticker.packId
},
"contentType": ContentType.Sticker.int
}
])
proc markAllRead*(chatId: string): string =
callPrivateRPC("markAllRead".prefix, %* [chatId])
proc markMessagesSeen*(chatId: string, messageIds: seq[string]): string =
callPrivateRPC("markMessagesSeen".prefix, %* [chatId, messageIds])
proc confirmJoiningGroup*(chatId: string): string =
callPrivateRPC("confirmJoiningGroup".prefix, %* [chatId])
proc leaveGroupChat*(chatId: string): string =
callPrivateRPC("leaveGroupChat".prefix, %* [nil, chatId, true])
proc clearChatHistory*(chatId: string): string =
callPrivateRPC("deleteMessagesByChatID".prefix, %* [chatId])
proc deleteMessage*(messageId: string): string =
callPrivateRPC("deleteMessage".prefix, %* [messageId])
proc renameGroup*(chatId: string, newName: string): string =
callPrivateRPC("changeGroupChatName".prefix, %* [nil, chatId, newName])
proc createGroup*(groupName: string, pubKeys: seq[string]): string =
callPrivateRPC("createGroupChatWithMembers".prefix, %* [nil, groupName, pubKeys])
proc createGroupChatFromInvitation*(groupName: string, chatID: string, adminPK: string): string =
callPrivateRPC("createGroupChatFromInvitation".prefix, %* [groupName, chatID, adminPK])
proc addGroupMembers*(chatId: string, pubKeys: seq[string]): string =
callPrivateRPC("addMembersToGroupChat".prefix, %* [nil, chatId, pubKeys])
proc kickGroupMember*(chatId: string, pubKey: string): string =
callPrivateRPC("removeMemberFromGroupChat".prefix, %* [nil, chatId, pubKey])
proc makeAdmin*(chatId: string, pubKey: string): string =
callPrivateRPC("addAdminsToGroupChat".prefix, %* [nil, chatId, [pubKey]])
proc updateOutgoingMessageStatus*(messageId: string, status: string): string =
result = callPrivateRPC("updateMessageOutgoingStatus".prefix, %* [messageId, status])
# TODO: handle errors
proc reSendChatMessage*(messageId: string): string =
result = callPrivateRPC("reSendChatMessage".prefix, %*[messageId])
proc muteChat*(chatId: string): string =
result = callPrivateRPC("muteChat".prefix, %*[chatId])
proc unmuteChat*(chatId: string): string =
result = callPrivateRPC("unmuteChat".prefix, %*[chatId])
proc getLinkPreviewData*(link: string, success: var bool): JsonNode =
let
responseStr = callPrivateRPC("getLinkPreviewData".prefix, %*[link])
response = Json.decode(responseStr, RpcResponseTyped[JsonNode], allowUnknownFields = false)
if not response.error.isNil:
success = false
return %* { "error": fmt"""Error getting link preview data for '{link}': {response.error.message}""" }
success = true
response.result
proc getAllComunities*(): seq[Community] =
var communities: seq[Community] = @[]
let rpcResult = callPrivateRPC("communities".prefix).parseJSON()
if rpcResult{"result"}.kind != JNull:
for jsonCommunity in rpcResult["result"]:
var community = jsonCommunity.toCommunity()
communities.add(community)
return communities
proc getJoinedComunities*(): seq[Community] =
var communities: seq[Community] = @[]
let rpcResult = callPrivateRPC("joinedCommunities".prefix).parseJSON()
if rpcResult{"result"}.kind != JNull:
for jsonCommunity in rpcResult["result"]:
var community = jsonCommunity.toCommunity()
communities.add(community)
return communities
proc createCommunity*(name: string, description: string, access: int, ensOnly: bool, color: string, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community =
let rpcResult = callPrivateRPC("createCommunity".prefix, %*[{
# TODO this will need to be renamed membership (small m)
"Membership": access,
"name": name,
"description": description,
"ensOnly": ensOnly,
"color": color,
"image": imageUrl,
"imageAx": aX,
"imageAy": aY,
"imageBx": bX,
"imageBy": bY
}]).parseJSON()
if rpcResult{"error"} != nil:
let error = Json.decode($rpcResult{"error"}, RpcError)
raise newException(RpcException, "Error creating community: " & error.message)
if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["communities"][0].toCommunity()
proc editCommunity*(communityId: string, name: string, description: string, access: int, ensOnly: bool, color: string, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community =
let rpcResult = callPrivateRPC("editCommunity".prefix, %*[{
# TODO this will need to be renamed membership (small m)
"CommunityID": communityId,
"Membership": access,
"name": name,
"description": description,
"ensOnly": ensOnly,
"color": color,
"image": imageUrl,
"imageAx": aX,
"imageAy": aY,
"imageBx": bX,
"imageBy": bY
}]).parseJSON()
if rpcResult{"error"} != nil:
let error = Json.decode($rpcResult{"error"}, RpcError)
raise newException(RpcException, "Error editing community: " & error.message)
if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["communities"][0].toCommunity()
proc createCommunityChannel*(communityId: string, name: string, description: string): Chat =
let rpcResult = callPrivateRPC("createCommunityChat".prefix, %*[
communityId,
{
"permissions": {
"access": 1 # TODO get this from user selected privacy setting
},
"identity": {
"display_name": name,
"description": description#,
# "color": color#,
# TODO add images once it is supported by Status-Go
# "images": [
# {
# "payload": image,
# # TODO get that from an enum
# "image_type": 1 # 1 is a raw payload
# }
# ]
}
}]).parseJSON()
if rpcResult{"error"} != nil:
let error = Json.decode($rpcResult{"error"}, RpcError)
raise newException(RpcException, "Error creating community channel: " & error.message)
if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["chats"][0].toChat()
proc editCommunityChannel*(communityId: string, channelId: string, name: string, description: string, categoryId: string, position: int): Chat =
let rpcResult = callPrivateRPC("editCommunityChat".prefix, %*[
communityId,
channelId.replace(communityId, ""),
{
"permissions": {
"access": 1 # TODO get this from user selected privacy setting
},
"identity": {
"display_name": name,
"description": description#,
# "color": color#,
# TODO add images once it is supported by Status-Go
# "images": [
# {
# "payload": image,
# # TODO get that from an enum
# "image_type": 1 # 1 is a raw payload
# }
# ]
},
"category_id": categoryId,
"position": position
}]).parseJSON()
if rpcResult{"error"} != nil:
let error = Json.decode($rpcResult{"error"}, RpcError)
raise newException(RpcException, "Error editing community channel: " & error.message)
if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["chats"][0].toChat()
proc deleteCommunityChat*(communityId: string, chatId: string) =
discard callPrivateRPC("deleteCommunityChat".prefix, %*[communityId, chatId])
proc createCommunityCategory*(communityId: string, name: string, channels: seq[string]): CommunityCategory =
let rpcResult = callPrivateRPC("createCommunityCategory".prefix, %*[
{
"communityId": communityId,
"categoryName": name,
"chatIds": channels
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
else:
for k, v in rpcResult["result"]["communityChanges"].getElems()[0]["categoriesAdded"].pairs():
result.id = v["category_id"].getStr()
result.name = v["name"].getStr()
result.position = v{"position"}.getInt()
proc editCommunityCategory*(communityId: string, categoryId: string, name: string, channels: seq[string]) =
let rpcResult = callPrivateRPC("editCommunityCategory".prefix, %*[
{
"communityId": communityId,
"categoryId": categoryId,
"categoryName": name,
"chatIds": channels
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
proc reorderCommunityChat*(communityId: string, categoryId: string, chatId: string, position: int) =
let rpcResult = callPrivateRPC("reorderCommunityChat".prefix, %*[
{
"communityId": communityId,
"categoryId": categoryId,
"chatId": chatId,
"position": position
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
proc reorderCommunityCategories*(communityId: string, categoryId: string, position: int) =
let rpcResult = callPrivateRPC("reorderCommunityCategories".prefix, %*[
{
"communityId": communityId,
"categoryId": categoryId,
"position": position
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
proc deleteCommunityCategory*(communityId: string, categoryId: string) =
let rpcResult = callPrivateRPC("deleteCommunityCategory".prefix, %*[
{
"communityId": communityId,
"categoryId": categoryId
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
proc requestCommunityInfo*(communityId: string) =
discard callPrivateRPC("requestCommunityInfoFromMailserver".prefix, %*[communityId])
proc joinCommunity*(communityId: string) =
discard callPrivateRPC("joinCommunity".prefix, %*[communityId])
proc leaveCommunity*(communityId: string) =
discard callPrivateRPC("leaveCommunity".prefix, %*[communityId])
proc inviteUsersToCommunity*(communityId: string, pubKeys: seq[string]): string =
callPrivateRPC("inviteUsersToCommunity".prefix, %*[{
"communityId": communityId,
"users": pubKeys
}])
proc exportCommunity*(communityId: string):string =
result = callPrivateRPC("exportCommunity".prefix, %*[communityId]).parseJson()["result"].getStr
proc importCommunity*(communityKey: string): string =
return callPrivateRPC("importCommunity".prefix, %*[communityKey])
proc removeUserFromCommunity*(communityId: string, pubKey: string) =
discard callPrivateRPC("removeUserFromCommunity".prefix, %*[communityId, pubKey])
proc requestToJoinCommunity*(communityId: string, ensName: string): seq[CommunityMembershipRequest] =
let rpcResult = callPrivateRPC("requestToJoinCommunity".prefix, %*[{
"communityId": communityId,
"ensName": ensName
}]).parseJSON()
var communityRequests: seq[CommunityMembershipRequest] = @[]
if rpcResult{"result"}{"requestsToJoinCommunity"} != nil and rpcResult{"result"}{"requestsToJoinCommunity"}.kind != JNull:
for jsonCommunityReqest in rpcResult["result"]["requestsToJoinCommunity"]:
communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())
return communityRequests
proc acceptRequestToJoinCommunity*(requestId: string) =
discard callPrivateRPC("acceptRequestToJoinCommunity".prefix, %*[{
"id": requestId
}])
proc declineRequestToJoinCommunity*(requestId: string) =
discard callPrivateRPC("declineRequestToJoinCommunity".prefix, %*[{
"id": requestId
}])
proc pendingRequestsToJoinForCommunity*(communityId: string): seq[CommunityMembershipRequest] =
let rpcResult = callPrivateRPC("pendingRequestsToJoinForCommunity".prefix, %*[communityId]).parseJSON()
var communityRequests: seq[CommunityMembershipRequest] = @[]
if rpcResult{"result"}.kind != JNull:
for jsonCommunityReqest in rpcResult["result"]:
communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())
return communityRequests
proc myPendingRequestsToJoin*(): seq[CommunityMembershipRequest] =
let rpcResult = callPrivateRPC("myPendingRequestsToJoin".prefix).parseJSON()
var communityRequests: seq[CommunityMembershipRequest] = @[]
if rpcResult.hasKey("result") and rpcResult{"result"}.kind != JNull:
for jsonCommunityReqest in rpcResult["result"]:
communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())
return communityRequests
proc banUserFromCommunity*(pubKey: string, communityId: string): string =
return callPrivateRPC("banUserFromCommunity".prefix, %*[{
"communityId": communityId,
"user": pubKey
}])
proc setCommunityMuted*(communityId: string, muted: bool) =
discard callPrivateRPC("setCommunityMuted".prefix, %*[communityId, muted])
proc rpcPinnedChatMessages*(chatId: string, cursorVal: string, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("chatPinnedMessages".prefix, %* [chatId, cursorVal, limit])
except RpcException as e:
success = false
result = e.msg
proc setPinMessage*(messageId: string, chatId: string, pinned: bool) =
discard callPrivateRPC("sendPinMessage".prefix, %*[{
"message_id": messageId,
"pinned": pinned,
"chat_id": chatId
}])
proc rpcActivityCenterNotifications*(cursorVal: JsonNode, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("activityCenterNotifications".prefix, %* [cursorVal, limit])
except RpcException as e:
success = false
result = e.msg
proc parseActivityCenterNotifications*(rpcResult: JsonNode): (string, seq[ActivityCenterNotification]) =
var notifs: seq[ActivityCenterNotification] = @[]
var msg: Message
if rpcResult{"notifications"}.kind != JNull:
for jsonMsg in rpcResult["notifications"]:
notifs.add(jsonMsg.toActivityCenterNotification())
return (rpcResult{"cursor"}.getStr, notifs)
proc activityCenterNotification*(cursor: string = ""): (string, seq[ActivityCenterNotification]) =
var cursorVal: JsonNode
if cursor == "":
cursorVal = newJNull()
else:
cursorVal = newJString(cursor)
var success: bool
let callResult = rpcActivityCenterNotifications(cursorVal, 20, success)
if success:
result = parseActivityCenterNotifications(callResult.parseJson()["result"])
proc markAllActivityCenterNotificationsRead*() =
discard callPrivateRPC("markAllActivityCenterNotificationsRead".prefix, %*[])
proc markActivityCenterNotificationsRead*(ids: seq[string]) =
discard callPrivateRPC("markActivityCenterNotificationsRead".prefix, %*[ids])
proc acceptActivityCenterNotifications*(ids: seq[string]): string =
result = callPrivateRPC("acceptActivityCenterNotifications".prefix, %*[ids])
proc dismissActivityCenterNotifications*(ids: seq[string]): string =
result = callPrivateRPC("dismissActivityCenterNotifications".prefix, %*[ids])
proc unreadActivityCenterNotificationsCount*(): int =
let rpcResult = callPrivateRPC("unreadActivityCenterNotificationsCount".prefix, %*[]).parseJson
if rpcResult{"result"}.kind != JNull:
return rpcResult["result"].getInt
proc asyncSearchMessages*(chatId: string, searchTerm: string, caseSensitive: bool, success: var bool): string =
success = true
try:
result = callPrivateRPC("allMessagesFromChatWhichMatchTerm".prefix, %* [chatId, searchTerm, caseSensitive])
except RpcException as e:
success = false
result = e.msg
proc asyncSearchMessages*(communityIds: seq[string], chatIds: seq[string], searchTerm: string, caseSensitive: bool, success: var bool): string =
success = true
try:
result = callPrivateRPC("allMessagesFromChatsAndCommunitiesWhichMatchTerm".prefix, %* [communityIds, chatIds, searchTerm, caseSensitive])
except RpcException as e:
success = false
result = e.msg