feature(@desktop/chat): support jumping to search result message that is not currently loaded in memory

Changes made here were necessary in order to have good base to requested develop feature.

- duplicated methods are removed
- loading messages either on start (initial loading) or loading more messages requested by
  scrolling in the chat view is now done in separate thread (asynchronous) instead as it was earlier,
  done in the main thread
- new file is added for storing async tasks for chat part only
- ChatModel (from status/chat.nim) is QObject instance now, so it may handle async job in a slot
- a job requested from the view is done in separate thread and view is notified about the changes
  using signal/slot mechanism

This is not refactoring, but minimal update to have clear situation for further changes

Fixes: #3005
This commit is contained in:
Sale Djenic 2021-07-22 14:41:45 +02:00 committed by Iuri Matias
parent 74d868ab92
commit e62465d86f
9 changed files with 787 additions and 798 deletions

View File

@ -12,7 +12,7 @@ proc handleChatEvents(self: ChatController) =
# Display already saved messages
self.status.events.on("messagesLoaded") do(e:Args):
let evArgs = MsgsLoadedArgs(e)
self.view.pushMessages(evArgs.messages)
self.view.onMessagesLoaded(evArgs.messages)
for statusUpdate in evArgs.statusUpdates:
self.view.communities.updateMemberVisibility(statusUpdate)
@ -22,6 +22,8 @@ proc handleChatEvents(self: ChatController) =
# Display already pinned messages
self.status.events.on("pinnedMessagesLoaded") do(e:Args):
self.view.pushPinnedMessages(MsgsLoadedArgs(e).messages)
self.status.events.on("searchMessagesLoaded") do(e:Args):
self.view.onSearchMessagesLoaded(MsgsLoadedArgs(e).messages)
self.status.events.on("activityCenterNotificationsLoaded") do(e:Args):
let notifications = ActivityCenterNotificationsArgs(e).activityCenterNotifications
@ -114,8 +116,7 @@ proc handleChatEvents(self: ChatController) =
if channel.chat.chatType == status_chat.ChatType.CommunityChat:
self.view.communities.updateCommunityChat(channel.chat)
self.view.asyncMessageLoad(channel.chat.id)
self.status.chat.loadInitialMessagesForChannel(channel.chat.id)
self.status.events.on("chatsLoaded") do(e:Args):
self.view.calculateUnreadMessages()
self.view.setActiveChannelByIndex(0)
@ -138,8 +139,8 @@ proc handleChatEvents(self: ChatController) =
elif channel.chat.chatType != ChatType.Profile:
discard self.view.channelView.chats.addChatItemToList(channel.chat)
self.view.setActiveChannel(channel.chat.id)
self.status.chat.chatMessages(channel.chat.id)
self.status.chat.chatReactions(channel.chat.id)
self.status.chat.loadInitialMessagesForChannel(channel.chat.id)
self.status.chat.statusUpdates()
self.status.events.on("channelLeft") do(e: Args):

View File

@ -266,7 +266,7 @@ QtObject:
proc pushChatItem*(self: ChatsView, chatItem: Chat) =
discard self.channelView.chats.addChatItemToList(chatItem)
self.messageView.messagePushed(self.messageView.messageList[chatItem.id].messages.len - 1)
self.messageView.messagePushed(self.messageView.messageList[chatItem.id].count - 1)
proc setTimelineChat*(self: ChatsView, chatItem: Chat) =
self.timelineChat = chatItem
@ -438,6 +438,12 @@ QtObject:
let task = RequestMessagesTaskArg( `method`: "requestMoreMessages", chatId: self.channelView.activeChannel.id)
mailserverWorker.start(task)
proc onMessagesLoaded*(self: ChatsView, messages: var seq[Message]) =
self.messageView.onMessagesLoaded(messages)
proc onSearchMessagesLoaded*(self: ChatsView, messages: seq[Message]) =
self.messageView.onSearchMessagesLoaded(messages)
proc pushMessages*(self: ChatsView, messages: var seq[Message]) =
self.messageView.pushMessages(messages)
@ -469,9 +475,6 @@ QtObject:
proc clearMessages*(self: ChatsView, id: string) =
self.messageView.clearMessages(id)
proc asyncMessageLoad*(self: ChatsView, chatId: string) {.slot.} =
self.messageView.asyncMessageLoad(chatId)
proc calculateUnreadMessages*(self: ChatsView) =
self.messageView.calculateUnreadMessages()

View File

@ -403,4 +403,12 @@ QtObject:
newQVariant(self.userList)
QtProperty[QVariant] userList:
read = getUserList
read = getUserList
proc getMessageIdWhichReplacedMessageWithId*(self: ChatMessageList, replacedMessageId: string): string =
## Returns id of the message which replaced a message with a certain id.
## Returns message id as a string or an empty string if there is no such message.
for message in self.messages:
if (message.replace == replacedMessageId):
return message.id

View File

@ -3,7 +3,6 @@ import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, o
import ../../../status/[status, contacts, types, mailservers]
import ../../../status/signals/types as signal_types
import ../../../status/ens as status_ens
import ../../../status/chat as status_chat
import ../../../status/messages as status_messages
import ../../../status/utils as status_utils
import ../../../status/chat/[chat, message]
@ -20,47 +19,6 @@ type
ChatViewRoles {.pure.} = enum
MessageList = UserRole + 1
type
AsyncMessageLoadTaskArg = ref object of QObjectTaskArg
chatId: string
const asyncMessageLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncMessageLoadTaskArg](argEncoded)
var messages: JsonNode
var msgCallSuccess: bool
let msgCallResult = status_chat.rpcChatMessages(arg.chatId, newJString(""), 20, msgCallSuccess)
if(msgCallSuccess):
messages = msgCallResult.parseJson()["result"]
var reactions: JsonNode
var reactionsCallSuccess: bool
let reactionsCallResult = status_chat.rpcReactions(arg.chatId, newJString(""), 20, reactionsCallSuccess)
if(reactionsCallSuccess):
reactions = reactionsCallResult.parseJson()["result"]
var pinnedMessages: JsonNode
var pinnedMessagesCallSuccess: bool
let pinnedMessagesCallResult = status_chat.rpcPinnedChatMessages(arg.chatId, newJString(""), 20, pinnedMessagesCallSuccess)
if(pinnedMessagesCallSuccess):
pinnedMessages = pinnedMessagesCallResult.parseJson()["result"]
let responseJson = %*{
"chatId": arg.chatId,
"messages": messages,
"reactions": reactions,
"pinnedMessages": pinnedMessages
}
arg.finish(responseJson)
proc asyncMessageLoad[T](self: T, slot: string, chatId: string) =
let arg = AsyncMessageLoadTaskArg(
tptr: cast[ByteAddress](asyncMessageLoadTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot,
chatId: chatId
)
self.status.tasks.threadpool.start(arg)
QtObject:
type MessageView* = ref object of QAbstractListModel
status: Status
@ -243,12 +201,12 @@ QtObject:
self.messageList[timelineChatId].add(msg)
# if self.channelView.activeChannel.id == timelineChatId: self.activeChannelChanged()
if self.channelView.activeChannel.id == timelineChatId: self.channelView.activeChannelChanged()
msgIndex = self.messageList[timelineChatId].messages.len - 1
msgIndex = self.messageList[timelineChatId].count - 1
else:
self.messageList[msg.chatId].add(msg)
if self.pinnedMessagesList[msg.chatId].contains(msg):
self.pinnedMessagesList[msg.chatId].add(msg)
msgIndex = self.messageList[msg.chatId].messages.len - 1
msgIndex = self.messageList[msg.chatId].count - 1
self.messagePushed(msgIndex)
if self.channelOpenTime.getOrDefault(msg.chatId, high(int64)) < msg.timestamp.parseFloat.fromUnixFloat.toUnix:
var channel = self.channelView.chats.getChannelById(msg.chatId)
@ -304,50 +262,15 @@ QtObject:
proc messagesLoaded*(self: MessageView) {.signal.}
proc loadMoreMessages*(self: MessageView) {.slot.} =
trace "Loading more messages", chaId = self.channelView.activeChannel.id
self.status.chat.chatMessages(self.channelView.activeChannel.id, false)
self.status.chat.chatReactions(self.channelView.activeChannel.id, false)
self.status.chat.statusUpdates()
self.messagesLoaded();
let channelId = self.channelView.activeChannel.id
self.status.chat.loadMoreMessagesForChannel(channelId)
proc loadMoreMessagesWithIndex*(self: MessageView, channelIndex: int) {.slot.} =
if (self.channelView.chats.chats.len == 0): return
let selectedChannel = self.channelView.getChannel(channelIndex)
if (selectedChannel == nil): return
trace "Loading more messages", chaId = selectedChannel.id
self.status.chat.chatMessages(selectedChannel.id, false)
self.status.chat.chatReactions(selectedChannel.id, false)
self.status.chat.statusUpdates()
proc onMessagesLoaded*(self: MessageView, messages: var seq[Message]) =
self.pushMessages(messages)
self.messagesLoaded();
proc loadingMessagesChanged*(self: MessageView, value: bool) {.signal.}
proc asyncMessageLoad*(self: MessageView, chatId: string) {.slot.} =
self.asyncMessageLoad("asyncMessageLoaded", chatId)
proc asyncMessageLoaded*(self: MessageView, rpcResponse: string) {.slot.} =
let
rpcResponseObj = rpcResponse.parseJson
chatId = rpcResponseObj{"chatId"}.getStr
if chatId == "": # .getStr() returns "" when field is not found
return
let messages = rpcResponseObj{"messages"}
if(messages != nil and messages.kind != JNull):
let chatMessages = status_chat.parseChatMessagesResponse(messages)
self.status.chat.chatMessages(chatId, true, chatMessages[0], chatMessages[1])
let rxns = rpcResponseObj{"reactions"}
if(rxns != nil and rxns.kind != JNull):
let reactions = status_chat.parseReactionsResponse(chatId, rxns)
self.status.chat.chatReactions(chatId, true, reactions[0], reactions[1])
let pinnedMsgs = rpcResponseObj{"pinnedMessages"}
if(pinnedMsgs != nil and pinnedMsgs.kind != JNull):
let pinnedMessages = status_chat.parseChatPinnedMessagesResponse(pinnedMsgs)
self.status.chat.pinnedMessagesByChatID(chatId, pinnedMessages[0], pinnedMessages[1])
proc hideLoadingIndicator*(self: MessageView) {.slot.} =
self.loadingMessages = false
self.loadingMessagesChanged(false)
@ -410,13 +333,12 @@ QtObject:
discard self.pinnedMessagesList[channelId].deleteMessage(messageId)
self.hideMessage(messageId)
proc deleteMessageWhichReplacedMessageWithId*(self: MessageView, channelId: string, messageId: string): bool =
var msgIdToBeDeleted: string
for message in self.messageList[channelId].messages:
if (message.replace == messageId):
msgIdToBeDeleted = message.id
break
proc deleteMessageWhichReplacedMessageWithId*(self: MessageView, channelId: string, replacedMessageId: string): bool =
## Deletes a message which replaced a message with id "replacedMessageId" from
## a channel with id "channelId" and returns true if such message is successfully
## deleted, otherwise returns false.
let msgIdToBeDeleted = self.messageList[channelId].getMessageIdWhichReplacedMessageWithId(replacedMessageId)
if (msgIdToBeDeleted.len == 0):
return false
@ -500,26 +422,6 @@ QtObject:
QtProperty[QVariant] searchResultMessageModel:
read = getSearchResultMessageModel
proc onSearchMessages*(self: MessageView, response: string) {.slot.} =
let responseObj = response.parseJson
if (responseObj.kind != JObject):
error "search messages response is not an json object"
return
let chatId = if(responseObj.contains("chatId")): responseObj{"chatId"}.getStr else : ""
if (chatId.len == 0):
error "search messages response either doesn't contain chatId or it is empty"
return
let messagesObj = if(responseObj.contains("messages")): responseObj{"messages"} else: newJObject()
if (messagesObj.kind != JObject):
error "search messages response either doesn't contain messages object or it is empty"
return
let (cursor, messages) = status_chat.parseChatMessagesResponse(messagesObj)
self.searchResultMessageModel.setFilteredMessages(messages)
proc searchMessages*(self: MessageView, searchTerm: string) {.slot.} =
if (searchTerm.len == 0):
self.searchResultMessageModel.clear(false)
@ -529,10 +431,8 @@ QtObject:
# later when we decide to apply message search over multiple channels MessageListProxyModel
# will be updated to support setting list of sourcer messages.
let chatId = self.channelView.activeChannel.id
let slot = SlotArg(
vptr: cast[ByteAddress](self.vptr),
slot: "onSearchMessages"
)
self.status.chat.asyncSearchMessages(chatId, searchTerm, false)
self.status.chat.asyncSearchMessages(slot, chatId, searchTerm, false)
proc onSearchMessagesLoaded*(self: MessageView, messages: seq[Message]) =
self.searchResultMessageModel.setFilteredMessages(messages)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
#################################################
# Async search messages by term
#################################################
type
AsyncSearchMessageTaskArg = ref object of QObjectTaskArg
chatId: string
searchTerm: string
caseSensitive: bool
const asyncSearchMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncSearchMessageTaskArg](argEncoded)
var messages: JsonNode
var success: bool
let response = status_chat.asyncSearchMessages(arg.chatId, arg.searchTerm, arg.caseSensitive, success)
if(success):
messages = response.parseJson()["result"]
let responseJson = %*{
"chatId": arg.chatId,
"messages": messages
}
arg.finish(responseJson)
#################################################
# Async load messages
#################################################
type
AsyncFetchChatMessagesTaskArg = ref object of QObjectTaskArg
chatId: string
chatCursor: string
emojiCursor: string
pinnedMsgCursor: string
limit: int
const asyncFetchChatMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncFetchChatMessagesTaskArg](argEncoded)
# handle messages
var chatMessagesObj: JsonNode
var chatCursor: string
var success: bool
var response = status_chat.fetchChatMessages(arg.chatId, arg.chatCursor, arg.limit, success)
var responseObj = response.parseJson()
if(success):
var resultObj: JsonNode
if (responseObj.getProp("result", resultObj)):
discard resultObj.getProp("cursor", chatCursor)
discard resultObj.getProp("messages", chatMessagesObj)
# handle reactions
var reactionsObj: JsonNode
var reactionsCursor: string
response = status_chat.rpcReactions(arg.chatId, arg.emojiCursor, arg.limit, success)
responseObj = response.parseJson()
if(success):
var resultObj: JsonNode
if (responseObj.getProp("result", resultObj)):
discard resultObj.getProp("cursor", reactionsCursor)
reactionsObj = resultObj
# handle pinned messages
var pinnedMsgObj: JsonNode
var pinnedMsgCursor: string
response = status_chat.rpcPinnedChatMessages(arg.chatId, arg.pinnedMsgCursor, arg.limit, success)
responseObj = response.parseJson()
if(success):
var resultObj: JsonNode
if (responseObj.getProp("result", resultObj)):
discard resultObj.getProp("cursor", pinnedMsgCursor)
discard resultObj.getProp("pinnedMessages", pinnedMsgObj)
let responseJson = %*{
"chatId": arg.chatId,
"messages": chatMessagesObj,
"messagesCursor": chatCursor,
"reactions": reactionsObj,
"reactionsCursor": reactionsCursor,
"pinnedMessages": pinnedMsgObj,
"pinnedMessagesCursor": pinnedMsgCursor
}
arg.finish(responseJson)

View File

@ -9,73 +9,36 @@ proc formatChatUpdate(response: JsonNode): (seq[Chat], seq[Message]) =
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"}{"messages"} != 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)
# This may be moved later to some common file.
# We may create a macro for these template procedures aslo.
template getProp(obj: JsonNode, prop: string, value: var typedesc[int]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):
value = obj[prop].getInt
success = true
success
template getProp(obj: JsonNode, prop: string, value: var typedesc[string]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):
value = obj[prop].getStr
success = true
success
proc emitUpdate(self: ChatModel, response: string) =
var (chats, messages) = self.processChatUpdate(parseJson(response))
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
template getProp(obj: JsonNode, prop: string, value: var typedesc[float]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):
value = obj[prop].getFloat
success = true
success
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, ChatType.Profile:
# 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 dont 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)
template getProp(obj: JsonNode, prop: string, value: var typedesc[JsonNode]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):
value = obj[prop]
success = true
success

View File

@ -73,14 +73,6 @@ proc loadChats*(): seq[Chat] =
result.add(chat)
result.sort(sortChats)
proc parseChatMessagesResponse*(rpcResult: JsonNode): (string, seq[Message]) =
var messages: seq[Message] = @[]
let messagesObj = rpcResult{"messages"}
if(messagesObj != nil and messagesObj.kind != JNull):
for jsonMsg in messagesObj:
messages.add(jsonMsg.toMessage())
return (rpcResult{"cursor"}.getStr, messages)
proc parseActivityCenterNotifications*(rpcResult: JsonNode): (string, seq[ActivityCenterNotification]) =
var notifs: seq[ActivityCenterNotification] = @[]
var msg: Message
@ -95,7 +87,7 @@ proc statusUpdates*(): seq[StatusUpdate] =
for jsonStatusUpdate in rpcResult["statusUpdates"]:
result.add(jsonStatusUpdate.toStatusUpdate)
proc rpcChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, success: var bool): string =
proc fetchChatMessages*(chatId: string, cursorVal: string, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("chatMessages".prefix, %* [chatId, cursorVal, limit])
@ -103,26 +95,6 @@ proc rpcChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, success:
success = false
result = e.msg
proc chatMessages*(chatId: string, cursor: string = ""): (string, seq[Message]) =
var cursorVal: JsonNode
if cursor == "":
cursorVal = newJNull()
else:
cursorVal = newJString(cursor)
var success: bool
let callResult = rpcChatMessages(chatId, cursorVal, 20, success)
if success:
result = parseChatMessagesResponse(callResult.parseJson()["result"])
proc parseReactionsResponse*(chatId: string, rpcResult: JsonNode): (string, seq[Reaction]) =
var reactions: seq[Reaction] = @[]
if rpcResult != nil and rpcResult.kind != JNull and rpcResult.len != 0:
for jsonMsg in rpcResult:
reactions.add(jsonMsg.toReaction)
return (rpcResult{"cursor"}.getStr, reactions)
proc editMessage*(messageId: string, msg: string): string =
callPrivateRPC("editMessage".prefix, %* [
{
@ -134,7 +106,7 @@ proc editMessage*(messageId: string, msg: string): string =
proc deleteMessageAndSend*(messageId: string): string =
callPrivateRPC("deleteMessageAndSend".prefix, %* [messageId])
proc rpcReactions*(chatId: string, cursorVal: JsonNode, limit: int, success: var bool): string =
proc rpcReactions*(chatId: string, cursorVal: string, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("emojiReactionsByChatID".prefix, %* [chatId, cursorVal, limit])
@ -142,19 +114,6 @@ proc rpcReactions*(chatId: string, cursorVal: JsonNode, limit: int, success: var
success = false
result = e.msg
proc getEmojiReactionsByChatId*(chatId: string, cursor: string = ""): (string, seq[Reaction]) =
var cursorVal: JsonNode
if cursor == "":
cursorVal = newJNull()
else:
cursorVal = newJString(cursor)
var success: bool
let rpcResult = rpcReactions(chatId, cursorVal, 20, success)
if success:
result = parseReactionsResponse(chatId, rpcResult.parseJson()["result"])
proc addEmojiReaction*(chatId: string, messageId: string, emojiId: int): seq[Reaction] =
let rpcResult = parseJson(callPrivateRPC("sendEmojiReaction".prefix, %* [chatId, messageId, emojiId]))["result"]
@ -552,18 +511,7 @@ proc banUserFromCommunity*(pubKey: string, communityId: string): string =
proc setCommunityMuted*(communityId: string, muted: bool) =
discard callPrivateRPC("setCommunityMuted".prefix, %*[communityId, muted])
proc parseChatPinnedMessagesResponse*(rpcResult: JsonNode): (string, seq[Message]) =
var messages: seq[Message] = @[]
let messagesObj = rpcResult{"pinnedMessages"}
if(messagesObj != nil and messagesObj.kind != JNull):
var msg: Message
for jsonMsg in messagesObj:
msg = jsonMsg["message"].toMessage()
msg.pinnedBy = $jsonMsg{"pinnedBy"}.getStr
messages.add(msg)
return (rpcResult{"cursor"}.getStr, messages)
proc rpcPinnedChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, success: var bool): string =
proc rpcPinnedChatMessages*(chatId: string, cursorVal: string, limit: int, success: var bool): string =
success = true
try:
result = callPrivateRPC("chatPinnedMessages".prefix, %* [chatId, cursorVal, limit])
@ -572,19 +520,6 @@ proc rpcPinnedChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, suc
success = false
result = e.msg
proc pinnedMessagesByChatID*(chatId: string, cursor: string): (string, seq[Message]) =
var cursorVal: JsonNode
if cursor == "":
cursorVal = newJNull()
else:
cursorVal = newJString(cursor)
var success: bool
let callResult = rpcPinnedChatMessages(chatId, cursorVal, 20, success)
if success:
result = parseChatPinnedMessagesResponse(callResult.parseJson()["result"])
proc setPinMessage*(messageId: string, chatId: string, pinned: bool) =
discard callPrivateRPC("sendPinMessage".prefix, %*[{
"message_id": messageId,

View File

@ -9,10 +9,6 @@ type
vptr*: ByteAddress
slot*: string
SlotArg* = ref object of RootObj
vptr*: ByteAddress
slot*: string
proc finish*[T](arg: QObjectTaskArg, payload: T) =
signal_handler(cast[pointer](arg.vptr), Json.encode(payload), arg.slot)