refactor: load gifs asynchronously
Instead of loading recent gifs right on startup eagerly, we postpone the task to when it's actually needed (when the gif popup is opened and the recent gifs tab is activated), and on top of that we also load the data asynchronously to keep the amount of work that needs to be done in a single tick as short as possible. This needs: status-im/status-go#3170 Closes #9437
This commit is contained in:
parent
2ac1216b68
commit
919f3dc6f7
|
@ -209,7 +209,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
|
|||
result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool,
|
||||
result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration)
|
||||
result.nodeService = node_service.newService(statusFoundation.events, result.settingsService, result.nodeConfigurationService)
|
||||
result.gifService = gif_service.newService(result.settingsService)
|
||||
result.gifService = gif_service.newService(result.settingsService, statusFoundation.events, statusFoundation.threadpool)
|
||||
result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool,
|
||||
result.settingsService, result.walletAccountService, result.transactionService,
|
||||
result.networkService, result.tokenService)
|
||||
|
|
|
@ -4,11 +4,13 @@ import ../../../../../../app_service/service/community/service as community_serv
|
|||
import ../../../../../../app_service/service/chat/service as chat_service
|
||||
import ../../../../../../app_service/service/gif/service as gif_service
|
||||
import ../../../../../../app_service/service/gif/dto
|
||||
import ../../../../../core/eventemitter
|
||||
|
||||
type
|
||||
Controller* = ref object of RootObj
|
||||
delegate: io_interface.AccessInterface
|
||||
sectionId: string
|
||||
events: EventEmitter
|
||||
chatId: string
|
||||
belongsToCommunity: bool
|
||||
communityService: community_service.Service
|
||||
|
@ -17,6 +19,7 @@ type
|
|||
|
||||
proc newController*(
|
||||
delegate: io_interface.AccessInterface,
|
||||
events: EventEmitter,
|
||||
sectionId: string,
|
||||
chatId: string,
|
||||
belongsToCommunity: bool,
|
||||
|
@ -26,6 +29,7 @@ proc newController*(
|
|||
): Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
result.events = events
|
||||
result.sectionId = chatId
|
||||
result.chatId = chatId
|
||||
result.belongsToCommunity = belongsToCommunity
|
||||
|
@ -37,7 +41,13 @@ proc delete*(self: Controller) =
|
|||
discard
|
||||
|
||||
proc init*(self: Controller) =
|
||||
discard
|
||||
self.events.on(SIGNAL_LOAD_RECENT_GIFS_DONE) do(e:Args):
|
||||
let args = GifsArgs(e)
|
||||
self.delegate.loadRecentGifsDone(args.gifs)
|
||||
|
||||
self.events.on(SIGNAL_LOAD_FAVORITE_GIFS_DONE) do(e:Args):
|
||||
let args = GifsArgs(e)
|
||||
self.delegate.loadFavoriteGifsDone(args.gifs)
|
||||
|
||||
proc getChatId*(self: Controller): string =
|
||||
return self.chatId
|
||||
|
@ -84,6 +94,12 @@ proc getTrendingsGifs*(self: Controller): seq[GifDto] =
|
|||
proc getRecentsGifs*(self: Controller): seq[GifDto] =
|
||||
return self.gifService.getRecents()
|
||||
|
||||
proc loadRecentGifs*(self: Controller) =
|
||||
self.gifService.asyncLoadRecentGifs()
|
||||
|
||||
proc loadFavoriteGifs*(self: Controller) =
|
||||
self.gifService.asyncLoadFavoriteGifs()
|
||||
|
||||
proc getFavoritesGifs*(self: Controller): seq[GifDto] =
|
||||
return self.gifService.getFavorites()
|
||||
|
||||
|
|
|
@ -50,9 +50,21 @@ method getTrendingsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
|
|||
method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method loadRecentGifs*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method loadRecentGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getFavoritesGifs*(self: AccessInterface): seq[GifDto] {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method loadFavoriteGifs*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method loadFavoriteGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method toggleFavoriteGif*(self: AccessInterface, item: GifDto) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import io_interface
|
|||
import ../io_interface as delegate_interface
|
||||
import view, controller
|
||||
import ../../../../../global/global_singleton
|
||||
import ../../../../../core/eventemitter
|
||||
|
||||
import ../../../../../../app_service/service/chat/service as chat_service
|
||||
import ../../../../../../app_service/service/community/service as community_service
|
||||
|
@ -21,6 +22,7 @@ type
|
|||
|
||||
proc newModule*(
|
||||
delegate: delegate_interface.AccessInterface,
|
||||
events: EventEmitter,
|
||||
sectionId: string,
|
||||
chatId: string,
|
||||
belongsToCommunity: bool,
|
||||
|
@ -33,7 +35,7 @@ proc newModule*(
|
|||
result.delegate = delegate
|
||||
result.view = view.newView(result)
|
||||
result.viewVariant = newQVariant(result.view)
|
||||
result.controller = controller.newController(result, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
|
||||
result.controller = controller.newController(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
|
||||
result.moduleLoaded = false
|
||||
|
||||
method delete*(self: Module) =
|
||||
|
@ -98,9 +100,21 @@ method getTrendingsGifs*(self: Module): seq[GifDto] =
|
|||
method getRecentsGifs*(self: Module): seq[GifDto] =
|
||||
return self.controller.getRecentsGifs()
|
||||
|
||||
method loadRecentGifs*(self: Module) =
|
||||
self.controller.loadRecentGifs()
|
||||
|
||||
method loadRecentGifsDone*(self: Module, gifs: seq[GifDto]) =
|
||||
self.view.updateGifColumns(gifs)
|
||||
|
||||
method getFavoritesGifs*(self: Module): seq[GifDto] =
|
||||
return self.controller.getFavoritesGifs()
|
||||
|
||||
method loadFavoriteGifs*(self: Module) =
|
||||
self.controller.loadFavoriteGifs()
|
||||
|
||||
method loadFavoriteGifsDone*(self: Module, gifs: seq[GifDto]) =
|
||||
self.view.updateGifColumns(gifs)
|
||||
|
||||
method toggleFavoriteGif*(self: Module, item: GifDto) =
|
||||
self.controller.toggleFavoriteGif(item)
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ QtObject:
|
|||
read = getGifColumnC
|
||||
notify = gifLoaded
|
||||
|
||||
proc updateGifColumns(self: View, data: seq[GifDto]) =
|
||||
proc updateGifColumns*(self: View, data: seq[GifDto]) =
|
||||
var columnAData: seq[GifDto] = @[]
|
||||
var columnAHeight = 0
|
||||
var columnBData: seq[GifDto] = @[]
|
||||
|
@ -115,11 +115,21 @@ QtObject:
|
|||
|
||||
proc getRecentsGifs*(self: View) {.slot.} =
|
||||
let data = self.delegate.getRecentsGifs()
|
||||
self.updateGifColumns(data)
|
||||
if data.len > 0:
|
||||
self.updateGifColumns(data)
|
||||
return
|
||||
|
||||
# recent gifs were not loaded yet, so we do it now
|
||||
self.delegate.loadRecentGifs()
|
||||
|
||||
proc getFavoritesGifs*(self: View) {.slot.} =
|
||||
let data = self.delegate.getFavoritesGifs()
|
||||
self.updateGifColumns(data)
|
||||
if data.len > 0:
|
||||
self.updateGifColumns(data)
|
||||
return
|
||||
|
||||
# favorite gifs were not loaded yet, so we do it now
|
||||
self.delegate.loadFavoriteGifs()
|
||||
|
||||
proc findGifDto(self: View, id: string): GifDto =
|
||||
for item in self.gifColumnAModel.gifs:
|
||||
|
|
|
@ -52,7 +52,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt
|
|||
isUsersListAvailable, settingsService, nodeConfigurationService, contactService, chatService, communityService, messageService)
|
||||
result.moduleLoaded = false
|
||||
|
||||
result.inputAreaModule = input_area_module.newModule(result, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
|
||||
result.inputAreaModule = input_area_module.newModule(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
|
||||
result.messagesModule = messages_module.newModule(result, events, sectionId, chatId, belongsToCommunity,
|
||||
contactService, communityService, chatService, messageService, mailserversService)
|
||||
result.usersModule =
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
include ../../common/json_utils
|
||||
include ../../../app/core/tasks/common
|
||||
import ../../../backend/gifs as status_go
|
||||
|
||||
type
|
||||
AsyncGetRecentGifsTaskArg = ref object of QObjectTaskArg
|
||||
|
||||
const asyncGetRecentGifsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncGetRecentGifsTaskArg](argEncoded)
|
||||
let response = status_go.getRecentGifs()
|
||||
arg.finish(response)
|
||||
|
||||
type
|
||||
AsyncGetFavoriteGifsTaskArg = ref object of QObjectTaskArg
|
||||
|
||||
const asyncGetFavoriteGifsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncGetFavoriteGifsTaskArg](argEncoded)
|
||||
let response = status_go.getFavoriteGifs()
|
||||
arg.finish(response)
|
|
@ -4,11 +4,17 @@ import os
|
|||
import uri
|
||||
import chronicles
|
||||
import sequtils
|
||||
import NimQml
|
||||
|
||||
import ../settings/service as settings_service
|
||||
import ../../../app/core/eventemitter
|
||||
import ../../../backend/backend
|
||||
import ../../../app/core/tasks/[qt, threadpool]
|
||||
import ../../../app/core/[main]
|
||||
import ./dto
|
||||
|
||||
include ./async_tasks
|
||||
|
||||
logScope:
|
||||
topics = "gif-service"
|
||||
|
||||
|
@ -23,127 +29,192 @@ let TENOR_API_KEY_RESOLVED =
|
|||
else:
|
||||
TENOR_API_KEY
|
||||
|
||||
const SIGNAL_LOAD_RECENT_GIFS_STARTED* = "loadRecentGifsStarted"
|
||||
const SIGNAL_LOAD_RECENT_GIFS_DONE* = "loadRecentGifsDone"
|
||||
|
||||
const SIGNAL_LOAD_FAVORITE_GIFS_STARTED* = "loadFavoriteGifsStarted"
|
||||
const SIGNAL_LOAD_FAVORITE_GIFS_DONE* = "loadFavoriteGifsDone"
|
||||
|
||||
type
|
||||
Service* = ref object of RootObj
|
||||
settingsService: settings_service.Service
|
||||
favorites: seq[GifDto]
|
||||
recents: seq[GifDto]
|
||||
GifsArgs* = ref object of Args
|
||||
gifs*: seq[GifDto]
|
||||
error*: string
|
||||
|
||||
proc delete*(self: Service) =
|
||||
discard
|
||||
QtObject:
|
||||
type
|
||||
Service* = ref object of QObject
|
||||
threadpool: ThreadPool
|
||||
settingsService: settings_service.Service
|
||||
favorites: seq[GifDto]
|
||||
recents: seq[GifDto]
|
||||
events: EventEmitter
|
||||
|
||||
proc newService*(settingsService: settings_service.Service): Service =
|
||||
result = Service()
|
||||
result.settingsService = settingsService
|
||||
result.favorites = @[]
|
||||
result.recents = @[]
|
||||
proc delete*(self: Service) =
|
||||
discard
|
||||
|
||||
proc setTenorAPIKey(self: Service) =
|
||||
try:
|
||||
let response = backend.setTenorAPIKey(TENOR_API_KEY_RESOLVED)
|
||||
if(not response.error.isNil):
|
||||
error "error setTenorAPIKey: ", errDescription = response.error.message
|
||||
proc newService*(settingsService: settings_service.Service, events: EventEmitter, threadpool: ThreadPool): Service =
|
||||
result = Service()
|
||||
result.QObject.setup
|
||||
result.settingsService = settingsService
|
||||
result.events = events
|
||||
result.threadpool = threadpool
|
||||
result.favorites = @[]
|
||||
result.recents = @[]
|
||||
|
||||
except Exception as e:
|
||||
error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg
|
||||
proc setTenorAPIKey(self: Service) =
|
||||
try:
|
||||
let response = backend.setTenorAPIKey(TENOR_API_KEY_RESOLVED)
|
||||
if(not response.error.isNil):
|
||||
error "error setTenorAPIKey: ", errDescription = response.error.message
|
||||
|
||||
proc getRecentGifs(self: Service) =
|
||||
try:
|
||||
let response = backend.getRecentGifs()
|
||||
except Exception as e:
|
||||
error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg
|
||||
|
||||
if(not response.error.isNil):
|
||||
error "error getRecentGifs: ", errDescription = response.error.message
|
||||
proc getRecentGifs(self: Service) =
|
||||
try:
|
||||
let response = backend.getRecentGifs()
|
||||
|
||||
self.recents = map(response.result.getElems(), settingToGifDto)
|
||||
if(not response.error.isNil):
|
||||
error "error getRecentGifs: ", errDescription = response.error.message
|
||||
|
||||
except Exception as e:
|
||||
error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
|
||||
self.recents = map(response.result.getElems(), settingToGifDto)
|
||||
|
||||
proc getFavoriteGifs(self: Service) =
|
||||
try:
|
||||
let response = backend.getFavoriteGifs()
|
||||
except Exception as e:
|
||||
error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
|
||||
|
||||
if(not response.error.isNil):
|
||||
error "error getFavoriteGifs: ", errDescription = response.error.message
|
||||
proc getFavoriteGifs(self: Service) =
|
||||
try:
|
||||
let response = backend.getFavoriteGifs()
|
||||
|
||||
self.favorites = map(response.result.getElems(), settingToGifDto)
|
||||
if(not response.error.isNil):
|
||||
error "error getFavoriteGifs: ", errDescription = response.error.message
|
||||
|
||||
except Exception as e:
|
||||
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
|
||||
self.favorites = map(response.result.getElems(), settingToGifDto)
|
||||
|
||||
proc init*(self: Service) =
|
||||
# set Tenor API Key
|
||||
self.setTenorAPIKey()
|
||||
except Exception as e:
|
||||
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
|
||||
|
||||
# get recent and favorite gifs on the database
|
||||
self.getRecentGifs()
|
||||
self.getFavoriteGifs()
|
||||
proc asyncLoadRecentGifs*(self: Service) =
|
||||
self.events.emit(SIGNAL_LOAD_RECENT_GIFS_STARTED, Args())
|
||||
try:
|
||||
let arg = AsyncGetRecentGifsTaskArg(
|
||||
tptr: cast[ByteAddress](asyncGetRecentGifsTask),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onAsyncGetRecentGifsDone"
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
except Exception as e:
|
||||
error "Error loading recent gifs", msg = e.msg
|
||||
|
||||
proc tenorQuery(self: Service, path: string): seq[GifDto] =
|
||||
try:
|
||||
let response = backend.fetchGifs(path)
|
||||
let doc = response.result.str.parseJson()
|
||||
proc onAsyncGetRecentGifsDone*(self: Service, response: string) {.slot.} =
|
||||
try:
|
||||
let rpcResponseObj = response.parseJson
|
||||
if (rpcResponseObj{"error"}.kind != JNull):
|
||||
let error = Json.decode($rpcResponseObj["error"], RpcError)
|
||||
error "error loading recent gifs", msg = error.message
|
||||
return
|
||||
|
||||
var items: seq[GifDto] = @[]
|
||||
for json in doc["results"]:
|
||||
items.add(tenorToGifDto(json))
|
||||
self.recents = map(rpcResponseObj{"result"}.getElems(), settingToGifDto)
|
||||
self.events.emit(SIGNAL_LOAD_RECENT_GIFS_DONE, GifsArgs(gifs: self.recents))
|
||||
except Exception as e:
|
||||
let errMsg = e.msg
|
||||
error "error: ", errMsg
|
||||
|
||||
return items
|
||||
except:
|
||||
return @[]
|
||||
proc asyncLoadFavoriteGifs*(self: Service) =
|
||||
self.events.emit(SIGNAL_LOAD_FAVORITE_GIFS_STARTED, Args())
|
||||
try:
|
||||
let arg = AsyncGetFavoriteGifsTaskArg(
|
||||
tptr: cast[ByteAddress](asyncGetFavoriteGifsTask),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onAsyncGetFavoriteGifsDone"
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
except Exception as e:
|
||||
error "Error loading favorite gifs", msg = e.msg
|
||||
|
||||
proc search*(self: Service, query: string): seq[GifDto] =
|
||||
return self.tenorQuery(fmt("search?q={encodeUrl(query)}"))
|
||||
proc onAsyncGetFavoriteGifsDone*(self: Service, response: string) {.slot.} =
|
||||
try:
|
||||
let rpcResponseObj = response.parseJson
|
||||
if (rpcResponseObj{"error"}.kind != JNull):
|
||||
let error = Json.decode($rpcResponseObj["error"], RpcError)
|
||||
error "error loading favorite gifs", msg = error.message
|
||||
return
|
||||
|
||||
proc getTrendings*(self: Service): seq[GifDto] =
|
||||
return self.tenorQuery("trending?")
|
||||
self.favorites = map(rpcResponseObj{"result"}.getElems(), settingToGifDto)
|
||||
self.events.emit(SIGNAL_LOAD_FAVORITE_GIFS_DONE, GifsArgs(gifs: self.favorites))
|
||||
except Exception as e:
|
||||
let errMsg = e.msg
|
||||
error "error: ", errMsg
|
||||
|
||||
proc getRecents*(self: Service): seq[GifDto] =
|
||||
return self.recents
|
||||
proc init*(self: Service) =
|
||||
# set Tenor API Key
|
||||
self.setTenorAPIKey()
|
||||
|
||||
proc addToRecents*(self: Service, gifDto: GifDto) =
|
||||
let recents = self.getRecents()
|
||||
var newRecents: seq[GifDto] = @[gifDto]
|
||||
var idx = 0
|
||||
proc tenorQuery(self: Service, path: string): seq[GifDto] =
|
||||
try:
|
||||
let response = backend.fetchGifs(path)
|
||||
let doc = response.result.str.parseJson()
|
||||
|
||||
while idx < MAX_RECENT - 1:
|
||||
if idx >= recents.len:
|
||||
break
|
||||
var items: seq[GifDto] = @[]
|
||||
for json in doc["results"]:
|
||||
items.add(tenorToGifDto(json))
|
||||
|
||||
if recents[idx].id == gifDto.id:
|
||||
return items
|
||||
except:
|
||||
return @[]
|
||||
|
||||
proc search*(self: Service, query: string): seq[GifDto] =
|
||||
return self.tenorQuery(fmt("search?q={encodeUrl(query)}"))
|
||||
|
||||
proc getTrendings*(self: Service): seq[GifDto] =
|
||||
return self.tenorQuery("trending?")
|
||||
|
||||
proc getRecents*(self: Service): seq[GifDto] =
|
||||
return self.recents
|
||||
|
||||
proc addToRecents*(self: Service, gifDto: GifDto) =
|
||||
let recents = self.getRecents()
|
||||
var newRecents: seq[GifDto] = @[gifDto]
|
||||
var idx = 0
|
||||
|
||||
while idx < MAX_RECENT - 1:
|
||||
if idx >= recents.len:
|
||||
break
|
||||
|
||||
if recents[idx].id == gifDto.id:
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
newRecents.add(recents[idx])
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
newRecents.add(recents[idx])
|
||||
idx += 1
|
||||
self.recents = newRecents
|
||||
let recent = %*{"items": map(newRecents, toJsonNode)}
|
||||
discard backend.updateRecentGifs(recent)
|
||||
|
||||
self.recents = newRecents
|
||||
let recent = %*{"items": map(newRecents, toJsonNode)}
|
||||
discard backend.updateRecentGifs(recent)
|
||||
proc getFavorites*(self: Service): seq[GifDto] =
|
||||
return self.favorites
|
||||
|
||||
proc getFavorites*(self: Service): seq[GifDto] =
|
||||
return self.favorites
|
||||
proc isFavorite*(self: Service, gifDto: GifDto): bool =
|
||||
for favorite in self.favorites:
|
||||
if favorite.id == gifDto.id:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc isFavorite*(self: Service, gifDto: GifDto): bool =
|
||||
for favorite in self.favorites:
|
||||
if favorite.id == gifDto.id:
|
||||
return true
|
||||
return false
|
||||
proc toggleFavorite*(self: Service, gifDto: GifDto) =
|
||||
var newFavorites: seq[GifDto] = @[]
|
||||
var found = false
|
||||
|
||||
proc toggleFavorite*(self: Service, gifDto: GifDto) =
|
||||
var newFavorites: seq[GifDto] = @[]
|
||||
var found = false
|
||||
for favoriteGif in self.getFavorites():
|
||||
if favoriteGif.id == gifDto.id:
|
||||
found = true
|
||||
continue
|
||||
|
||||
for favoriteGif in self.getFavorites():
|
||||
if favoriteGif.id == gifDto.id:
|
||||
found = true
|
||||
continue
|
||||
newFavorites.add(favoriteGif)
|
||||
|
||||
newFavorites.add(favoriteGif)
|
||||
if not found:
|
||||
newFavorites.add(gifDto)
|
||||
|
||||
if not found:
|
||||
newFavorites.add(gifDto)
|
||||
|
||||
self.favorites = newFavorites
|
||||
let favorites = %*{"items": map(newFavorites, toJsonNode)}
|
||||
discard backend.updateFavoriteGifs(favorites)
|
||||
self.favorites = newFavorites
|
||||
let favorites = %*{"items": map(newFavorites, toJsonNode)}
|
||||
discard backend.updateFavoriteGifs(favorites)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import json, strutils
|
||||
import core
|
||||
import response_type
|
||||
|
||||
export response_type
|
||||
|
||||
proc getRecentGifs*(): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %* []
|
||||
result = callPrivateRPC("gif_getRecentGifs", payload)
|
||||
|
||||
proc getFavoriteGifs*(): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %* []
|
||||
result = callPrivateRPC("gif_getFavoriteGifs", payload)
|
Loading…
Reference in New Issue