Switch to new URL unfurl API [sending messages part] (#11476)
This commit is contained in:
parent
3a0954c5da
commit
d7aa0582be
|
@ -1,11 +1,14 @@
|
|||
import io_interface
|
||||
import io_interface, chronicles, tables
|
||||
|
||||
import ../../../../../../app_service/service/message/service as message_service
|
||||
import ../../../../../../app_service/service/community/service as community_service
|
||||
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 ../../../../../../app_service/service/message/dto/link_preview
|
||||
import ../../../../../core/eventemitter
|
||||
import ../../../../../core/unique_event_emitter
|
||||
import ./link_preview_cache
|
||||
|
||||
type
|
||||
Controller* = ref object of RootObj
|
||||
|
@ -17,6 +20,8 @@ type
|
|||
communityService: community_service.Service
|
||||
chatService: chat_service.Service
|
||||
gifService: gif_service.Service
|
||||
messageService: message_service.Service
|
||||
linkPreviewCache: LinkPreviewCache
|
||||
|
||||
proc newController*(
|
||||
delegate: io_interface.AccessInterface,
|
||||
|
@ -26,7 +31,8 @@ proc newController*(
|
|||
belongsToCommunity: bool,
|
||||
chatService: chat_service.Service,
|
||||
communityService: community_service.Service,
|
||||
gifService: gif_service.Service
|
||||
gifService: gif_service.Service,
|
||||
messageService: message_service.Service
|
||||
): Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
|
@ -37,6 +43,10 @@ proc newController*(
|
|||
result.chatService = chatService
|
||||
result.communityService = communityService
|
||||
result.gifService = gifService
|
||||
result.messageService = messageService
|
||||
result.linkPreviewCache = newLinkPreiewCache()
|
||||
|
||||
proc onUrlsUnfurled(self: Controller, args: LinkPreviewV2DataArgs)
|
||||
|
||||
proc delete*(self: Controller) =
|
||||
self.events.disconnect()
|
||||
|
@ -70,6 +80,10 @@ proc init*(self: Controller) =
|
|||
self.events.on(SIGNAL_SEARCH_GIFS_ERROR) do(e:Args):
|
||||
self.delegate.searchGifsError()
|
||||
|
||||
self.events.on(SIGNAL_URLS_UNFURLED) do(e:Args):
|
||||
let args = LinkPreviewV2DataArgs(e)
|
||||
self.onUrlsUnfurled(args)
|
||||
|
||||
proc getChatId*(self: Controller): string =
|
||||
return self.chatId
|
||||
|
||||
|
@ -86,7 +100,16 @@ proc sendChatMessage*(
|
|||
contentType: int,
|
||||
preferredUsername: string = "") =
|
||||
|
||||
self.chatService.sendChatMessage(self.chatId, msg, replyTo, contentType, preferredUsername)
|
||||
let urls = self.messageService.getTextUrls(msg)
|
||||
let linkPreviews = self.linkPreviewCache.linkPreviewsSeq(urls)
|
||||
|
||||
self.chatService.sendChatMessage(self.chatId,
|
||||
msg,
|
||||
replyTo,
|
||||
contentType,
|
||||
preferredUsername,
|
||||
linkPreviews
|
||||
)
|
||||
|
||||
proc requestAddressForTransaction*(self: Controller, fromAddress: string, amount: string, tokenAddress: string) =
|
||||
self.chatService.requestAddressForTransaction(self.chatId, fromAddress, amount, tokenAddress)
|
||||
|
@ -132,3 +155,20 @@ proc addToRecentsGif*(self: Controller, item: GifDto) =
|
|||
|
||||
proc isFavorite*(self: Controller, item: GifDto): bool =
|
||||
return self.gifService.isFavorite(item)
|
||||
|
||||
proc setText*(self: Controller, text: string) =
|
||||
let urls = self.messageService.getTextUrls(text)
|
||||
self.delegate.setUrls(urls)
|
||||
if len(urls) > 0:
|
||||
let newUrls = self.linkPreviewCache.unknownUrls(urls)
|
||||
self.messageService.asyncUnfurlUrls(newUrls)
|
||||
|
||||
proc linkPreviewsFromCache*(self: Controller, urls: seq[string]): Table[string, LinkPreview] =
|
||||
return self.linkPreviewCache.linkPreviews(urls)
|
||||
|
||||
proc clearLinkPreviewCache*(self: Controller) =
|
||||
self.linkPreviewCache.clear()
|
||||
|
||||
proc onUrlsUnfurled(self: Controller, args: LinkPreviewV2DataArgs) =
|
||||
let urls = self.linkPreviewCache.add(args.linkPreviews)
|
||||
self.delegate.updateLinkPreviewsFromCache(urls)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import NimQml
|
||||
import NimQml, tables
|
||||
|
||||
import ../../../../../../app_service/service/gif/dto
|
||||
import ../../../../../../app_service/service/message/dto/link_preview
|
||||
|
||||
type
|
||||
AccessInterface* {.pure inheritable.} = ref object of RootObj
|
||||
|
@ -94,3 +95,18 @@ method isFavorite*(self: AccessInterface, item: GifDto): bool {.base.} =
|
|||
|
||||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setText*(self: AccessInterface, text: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setUrls*(self: AccessInterface, urls: seq[string]) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method updateLinkPreviewsFromCache*(self: AccessInterface, urls: seq[string]) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method clearLinkPreviewCache*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method linkPreviewsFromCache*(self: AccessInterface, urls: seq[string]): Table[string, LinkPreview] {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import tables
|
||||
import ../../../../../../app_service/service/message/dto/link_preview
|
||||
|
||||
type
|
||||
LinkPreviewCache* = ref object
|
||||
cache: Table[string, LinkPreview]
|
||||
|
||||
proc newLinkPreiewCache*(): LinkPreviewCache =
|
||||
result = LinkPreviewCache()
|
||||
result.cache = initTable[string, LinkPreview]()
|
||||
|
||||
# Returns a table of link previews for given `urls`.
|
||||
# If url is not found in cache, it's skipped
|
||||
proc linkPreviews*(self: LinkPreviewCache, urls: seq[string]): Table[string, LinkPreview] =
|
||||
result = initTable[string, LinkPreview]()
|
||||
for url in urls:
|
||||
if self.cache.hasKey(url):
|
||||
result[url] = self.cache[url]
|
||||
|
||||
# Returns list of link previews for given `urls`.
|
||||
# If url is not found in cache, it's skipped
|
||||
proc linkPreviewsSeq*(self: LinkPreviewCache, urls: seq[string]): seq[LinkPreview] =
|
||||
for url in urls:
|
||||
if self.cache.hasKey(url):
|
||||
result.add(self.cache[url])
|
||||
|
||||
# Adds all given link previews to cache.
|
||||
# Returns list of urls, for which link preview was updated.
|
||||
# If a url is already found in cache, correcponding link preview is updated.
|
||||
proc add*(self: LinkPreviewCache, linkPreviews: Table[string, LinkPreview]): seq[string] =
|
||||
for key, value in pairs(linkPreviews):
|
||||
result.add(key)
|
||||
self.cache[key] = value
|
||||
|
||||
# Goes though given `urls` and returns a list
|
||||
# of urls not found in cache.
|
||||
proc unknownUrls*(self: LinkPreviewCache, urls: seq[string]): seq[string] =
|
||||
for url in urls:
|
||||
if not self.cache.hasKey(url):
|
||||
result.add(url)
|
||||
|
||||
# Clears link preview cache
|
||||
proc clear*(self: LinkPreviewCache) =
|
||||
self.cache.clear()
|
|
@ -0,0 +1,22 @@
|
|||
import strformat
|
||||
import ../../../../../../app_service/service/message/dto/link_preview
|
||||
|
||||
type
|
||||
Item* = ref object
|
||||
unfurled*: bool
|
||||
linkPreview*: LinkPreview
|
||||
|
||||
proc delete*(self: Item) =
|
||||
self.linkPreview.delete
|
||||
|
||||
proc linkPreview*(self: Item): LinkPreview {.inline.} =
|
||||
return self.linkPreview
|
||||
|
||||
proc `linkPreview=`*(self: Item, linkPreview: LinkPreview) {.inline.} =
|
||||
self.linkPreview = linkPreview
|
||||
|
||||
proc `$`*(self: Item): string =
|
||||
result = fmt"""LinkPreviewItem(
|
||||
unfurled: {self.unfurled},
|
||||
linkPreview: {self.linkPreview},
|
||||
)"""
|
|
@ -0,0 +1,140 @@
|
|||
import NimQml, strformat, tables
|
||||
import ./link_preview_item
|
||||
import ../../../../../../app_service/service/message/dto/link_preview
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
Url = UserRole + 1
|
||||
Unfurled
|
||||
Hostname
|
||||
Title
|
||||
Description
|
||||
ThumbnailWidth
|
||||
ThumbnailHeight
|
||||
ThumbnailUrl
|
||||
ThumbnailDataUri
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Model* = ref object of QAbstractListModel
|
||||
items: seq[Item]
|
||||
|
||||
proc delete*(self: Model) =
|
||||
for i in 0 ..< self.items.len:
|
||||
self.items[i].delete
|
||||
self.items = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: Model) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newLinkPreviewModel*(): Model =
|
||||
new(result, delete)
|
||||
result.setup
|
||||
|
||||
proc newModel*(): Model =
|
||||
new(result, delete)
|
||||
result.setup
|
||||
|
||||
proc `$`*(self: Model): string =
|
||||
for i in 0 ..< self.items.len:
|
||||
result &= fmt"""
|
||||
[{i}]:({$self.items[i]})
|
||||
"""
|
||||
|
||||
proc countChanged(self: Model) {.signal.}
|
||||
|
||||
proc getCount*(self: Model): int {.slot.} =
|
||||
self.items.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = getCount
|
||||
notify = countChanged
|
||||
|
||||
method rowCount(self: Model, index: QModelIndex = nil): int =
|
||||
return self.items.len
|
||||
|
||||
method roleNames(self: Model): Table[int, string] =
|
||||
{
|
||||
ModelRole.Url.int:"url",
|
||||
ModelRole.Unfurled.int:"unfurled",
|
||||
ModelRole.Hostname.int:"hostname",
|
||||
ModelRole.Title.int:"title",
|
||||
ModelRole.Description.int:"description",
|
||||
ModelRole.ThumbnailWidth.int:"thumbnailWidth",
|
||||
ModelRole.ThumbnailHeight.int:"thumbnailHeight",
|
||||
ModelRole.ThumbnailUrl.int:"thumbnailUrl",
|
||||
ModelRole.ThumbnailDataUri.int:"thumbnailDataUri",
|
||||
}.toTable
|
||||
|
||||
method allLinkPreviewRoles(self: Model): seq[int] =
|
||||
return @[
|
||||
Unfurled.int,
|
||||
Hostname.int,
|
||||
Title.int,
|
||||
Description.int,
|
||||
ThumbnailWidth.int,
|
||||
ThumbnailHeight.int,
|
||||
ThumbnailUrl.int,
|
||||
ThumbnailDataUri.int,
|
||||
]
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
if (not index.isValid):
|
||||
return
|
||||
|
||||
if (index.row < 0 or index.row >= self.items.len):
|
||||
return
|
||||
|
||||
let item = self.items[index.row]
|
||||
let enumRole = role.ModelRole
|
||||
|
||||
case enumRole:
|
||||
of ModelRole.Url:
|
||||
result = newQVariant(item.linkPreview.url)
|
||||
of ModelRole.Unfurled:
|
||||
result = newQVariant(item.unfurled)
|
||||
of ModelRole.Hostname:
|
||||
result = newQVariant(item.linkPreview.hostname)
|
||||
of ModelRole.Title:
|
||||
result = newQVariant(item.linkPreview.title)
|
||||
of ModelRole.Description:
|
||||
result = newQVariant(item.linkPreview.description)
|
||||
of ModelRole.ThumbnailWidth:
|
||||
result = newQVariant(item.linkPreview.thumbnail.width)
|
||||
of ModelRole.ThumbnailHeight:
|
||||
result = newQVariant(item.linkPreview.thumbnail.height)
|
||||
of ModelRole.ThumbnailUrl:
|
||||
result = newQVariant(item.linkPreview.thumbnail.url)
|
||||
of ModelRole.ThumbnailDataUri:
|
||||
result = newQVariant(item.linkPreview.thumbnail.dataUri)
|
||||
|
||||
proc clearItems*(self: Model) =
|
||||
self.beginResetModel()
|
||||
self.items = @[]
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
proc setUrls*(self: Model, urls: seq[string]) =
|
||||
var items: seq[Item]
|
||||
for url in urls:
|
||||
let linkPreview = initLinkPreview(url)
|
||||
var item = Item()
|
||||
item.unfurled = false
|
||||
item.linkPreview = linkPreview
|
||||
items.add(item)
|
||||
|
||||
self.beginResetModel()
|
||||
self.items = items
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
proc updateLinkPreviews*(self: Model, linkPreviews: Table[string, LinkPreview]) =
|
||||
for row, item in self.items:
|
||||
if not linkPreviews.hasKey(item.linkPreview.url):
|
||||
continue
|
||||
item.unfurled = true
|
||||
item.linkPreview = linkPreviews[item.linkPreview.url]
|
||||
let modelIndex = self.createIndex(row, 0, nil)
|
||||
defer: modelIndex.delete
|
||||
self.dataChanged(modelIndex, modelIndex, self.allLinkPreviewRoles())
|
|
@ -1,10 +1,12 @@
|
|||
import NimQml
|
||||
import NimQml, tables
|
||||
import io_interface
|
||||
import ../io_interface as delegate_interface
|
||||
import view, controller
|
||||
import ../../../../../global/global_singleton
|
||||
import ../../../../../core/eventemitter
|
||||
|
||||
import ../../../../../../app_service/service/message/service as message_service
|
||||
import ../../../../../../app_service/service/message/dto/link_preview
|
||||
import ../../../../../../app_service/service/chat/service as chat_service
|
||||
import ../../../../../../app_service/service/community/service as community_service
|
||||
import ../../../../../../app_service/service/gif/service as gif_service
|
||||
|
@ -29,13 +31,14 @@ proc newModule*(
|
|||
chatService: chat_service.Service,
|
||||
communityService: community_service.Service,
|
||||
gifService: gif_service.Service,
|
||||
messageService: message_service.Service
|
||||
):
|
||||
Module =
|
||||
result = Module()
|
||||
result.delegate = delegate
|
||||
result.view = view.newView(result)
|
||||
result.viewVariant = newQVariant(result.view)
|
||||
result.controller = controller.newController(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
|
||||
result.controller = controller.newController(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService, messageService)
|
||||
result.moduleLoaded = false
|
||||
|
||||
method delete*(self: Module) =
|
||||
|
@ -147,3 +150,18 @@ method addToRecentsGif*(self: Module, item: GifDto) =
|
|||
|
||||
method isFavorite*(self: Module, item: GifDto): bool =
|
||||
return self.controller.isFavorite(item)
|
||||
|
||||
method setText*(self: Module, text: string) =
|
||||
self.controller.setText(text)
|
||||
|
||||
method clearLinkPreviewCache*(self: Module) {.slot.} =
|
||||
self.controller.clearLinkPreviewCache()
|
||||
|
||||
method updateLinkPreviewsFromCache*(self: Module, urls: seq[string]) =
|
||||
self.view.updateLinkPreviewsFromCache(urls)
|
||||
|
||||
method setUrls*(self: Module, urls: seq[string]) =
|
||||
self.view.setUrls(urls)
|
||||
|
||||
method linkPreviewsFromCache*(self: Module, urls: seq[string]): Table[string, LinkPreview] =
|
||||
return self.controller.linkPreviewsFromCache(urls)
|
||||
|
|
|
@ -16,7 +16,7 @@ QtObject:
|
|||
|
||||
proc newPreservedProperties*(): PreservedProperties =
|
||||
new(result, delete)
|
||||
result.QObject.setup
|
||||
result.setup
|
||||
|
||||
proc textChanged*(self: PreservedProperties) {.signal.}
|
||||
proc setText*(self: PreservedProperties, value: string) {.slot.} =
|
||||
|
|
|
@ -2,6 +2,7 @@ import NimQml
|
|||
import ./io_interface
|
||||
import ./gif_column_model
|
||||
import ./preserved_properties
|
||||
import ./link_preview_model as link_preview_model
|
||||
import ../../../../../../app_service/service/gif/dto
|
||||
|
||||
QtObject:
|
||||
|
@ -14,6 +15,8 @@ QtObject:
|
|||
gifLoading: bool
|
||||
preservedProperties: PreservedProperties
|
||||
preservedPropertiesVariant: QVariant
|
||||
linkPreviewModel: link_preview_model.Model
|
||||
linkPreviewModelVariant: QVariant
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.QObject.delete
|
||||
|
@ -22,6 +25,8 @@ QtObject:
|
|||
self.gifColumnCModel.delete
|
||||
self.preservedProperties.delete
|
||||
self.preservedPropertiesVariant.delete
|
||||
self.linkPreviewModel.delete
|
||||
self.linkPreviewModelVariant.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
new(result, delete)
|
||||
|
@ -33,6 +38,8 @@ QtObject:
|
|||
result.gifLoading = false
|
||||
result.preservedProperties = newPreservedProperties()
|
||||
result.preservedPropertiesVariant = newQVariant(result.preservedProperties)
|
||||
result.linkPreviewModel = newLinkPreviewModel()
|
||||
result.linkPreviewModelVariant = newQVariant(result.linkPreviewModel)
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
|
@ -183,3 +190,25 @@ QtObject:
|
|||
|
||||
QtProperty[QVariant] preservedProperties:
|
||||
read = getPreservedProperties
|
||||
|
||||
proc getLinkPreviewModel*(self: View): QVariant {.slot.} =
|
||||
return self.linkPreviewModelVariant
|
||||
|
||||
QtProperty[QVariant] linkPreviewModel:
|
||||
read = getLinkPreviewModel
|
||||
|
||||
# Currently used to fetch link previews, but could be used elsewhere
|
||||
proc setText*(self: View, text: string) {.slot.} =
|
||||
self.delegate.setText(text)
|
||||
|
||||
proc updateLinkPreviewsFromCache*(self: View, urls: seq[string]) =
|
||||
let linkPreviews = self.delegate.linkPreviewsFromCache(urls)
|
||||
self.linkPreviewModel.updateLinkPreviews(linkPreviews)
|
||||
|
||||
proc setUrls*(self: View, urls: seq[string]) =
|
||||
self.linkPreviewModel.setUrls(urls)
|
||||
self.updateLinkPreviewsFromCache(urls)
|
||||
|
||||
proc clearLinkPreviewCache*(self: View) {.slot.} =
|
||||
self.delegate.clearLinkPreviewCache()
|
||||
|
||||
|
|
|
@ -281,7 +281,7 @@ proc editMessage*(self: Controller, messageId: string, contentType: int, updated
|
|||
|
||||
proc getLinkPreviewData*(self: Controller, link: string, uuid: string, whiteListedSites: string, whiteListedImgExtensions: string, unfurlImages: bool): string =
|
||||
self.messageService.asyncGetLinkPreviewData(link, uuid, whiteListedSites, whiteListedImgExtensions, unfurlImages)
|
||||
|
||||
|
||||
proc getSearchedMessageId*(self: Controller): string =
|
||||
return self.searchedMessageId
|
||||
|
||||
|
|
|
@ -55,7 +55,8 @@ 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, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
|
||||
result.inputAreaModule = input_area_module.newModule(result, events, sectionId, chatId, belongsToCommunity,
|
||||
chatService, communityService, gifService, messageService)
|
||||
result.messagesModule = messages_module.newModule(result, events, sectionId, chatId, belongsToCommunity,
|
||||
contactService, communityService, chatService, messageService, mailserversService)
|
||||
result.usersModule =
|
||||
|
|
|
@ -4,6 +4,7 @@ import std/[times, os]
|
|||
import ../../../app/core/tasks/[qt, threadpool]
|
||||
import ./dto/chat as chat_dto
|
||||
import ../message/dto/message as message_dto
|
||||
import ../message/dto/link_preview
|
||||
import ../activity_center/dto/notification as notification_dto
|
||||
import ../community/dto/community as community_dto
|
||||
import ../contacts/service as contact_service
|
||||
|
@ -508,6 +509,7 @@ QtObject:
|
|||
replyTo: string,
|
||||
contentType: int,
|
||||
preferredUsername: string = "",
|
||||
linkPreviews: seq[LinkPreview] = @[],
|
||||
communityId: string = "") =
|
||||
try:
|
||||
let allKnownContacts = self.contactService.getContactsByGroup(ContactsGroup.AllKnownContacts)
|
||||
|
@ -519,6 +521,7 @@ QtObject:
|
|||
replyTo,
|
||||
contentType,
|
||||
preferredUsername,
|
||||
linkPreviews,
|
||||
communityId) # Only send a community ID for the community invites
|
||||
|
||||
let (chats, messages) = self.processMessageUpdateAfterSend(response)
|
||||
|
|
|
@ -283,4 +283,32 @@ const asyncGetFirstUnseenMessageIdForTaskArg: Task = proc(argEncoded: string) {.
|
|||
errDesription = e.msg, chatId=arg.chatId
|
||||
responseJson["error"] = %e.msg
|
||||
|
||||
arg.finish(responseJson)
|
||||
arg.finish(responseJson)
|
||||
|
||||
|
||||
#################################################
|
||||
# Async unfurl urls
|
||||
#################################################
|
||||
|
||||
type
|
||||
AsyncUnfurlUrlsTaskArg = ref object of QObjectTaskArg
|
||||
urls*: seq[string]
|
||||
|
||||
const asyncUnfurlUrlsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncUnfurlUrlsTaskArg](argEncoded)
|
||||
try:
|
||||
let response = status_go.unfurlUrls(arg.urls)
|
||||
let output = %*{
|
||||
"error": (if response.error != nil: response.error.message else: ""),
|
||||
"response": response.result,
|
||||
"requestedUrls": %*arg.urls
|
||||
}
|
||||
arg.finish(output)
|
||||
except Exception as e:
|
||||
error "unfurlUrlsTask failed", message = e.msg
|
||||
let output = %*{
|
||||
"error": e.msg,
|
||||
"response": "",
|
||||
"requestedUrls": %*arg.urls
|
||||
}
|
||||
arg.finish(output)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import json, strformat, tables
|
||||
include ../../../common/json_utils
|
||||
|
||||
type
|
||||
LinkPreviewThumbnail* = object
|
||||
width*: int
|
||||
height*: int
|
||||
url*: string
|
||||
dataUri*: string
|
||||
|
||||
type
|
||||
LinkPreview* = ref object
|
||||
url*: string
|
||||
hostname*: string
|
||||
title*: string
|
||||
description*: string
|
||||
thumbnail*: LinkPreviewThumbnail
|
||||
|
||||
proc delete*(self: LinkPreview) =
|
||||
discard
|
||||
|
||||
proc initLinkPreview*(url: string): LinkPreview =
|
||||
result = LinkPreview()
|
||||
result.url = url
|
||||
|
||||
proc toLinkPreviewThumbnail*(jsonObj: JsonNode): LinkPreviewThumbnail =
|
||||
result = LinkPreviewThumbnail()
|
||||
discard jsonObj.getProp("width", result.width)
|
||||
discard jsonObj.getProp("height", result.height)
|
||||
discard jsonObj.getProp("url", result.url)
|
||||
discard jsonObj.getProp("dataUri", result.dataUri)
|
||||
|
||||
proc toLinkPreview*(jsonObj: JsonNode): LinkPreview =
|
||||
result = LinkPreview()
|
||||
discard jsonObj.getProp("url", result.url)
|
||||
discard jsonObj.getProp("hostname", result.hostname)
|
||||
discard jsonObj.getProp("title", result.title)
|
||||
discard jsonObj.getProp("description", result.description)
|
||||
discard jsonObj.getProp("hostname", result.hostname)
|
||||
|
||||
var thumbnail: JsonNode
|
||||
if jsonObj.getProp("thumbnail", thumbnail):
|
||||
result.thumbnail = toLinkPreviewThumbnail(thumbnail)
|
||||
|
||||
proc `$`*(self: LinkPreviewThumbnail): string =
|
||||
result = fmt"""LinkPreviewThumbnail(
|
||||
width: {self.width},
|
||||
height: {self.height},
|
||||
url: {self.url},
|
||||
dataUri: {self.dataUri}
|
||||
)"""
|
||||
|
||||
proc `$`*(self: LinkPreview): string =
|
||||
result = fmt"""LinkPreview(
|
||||
url: {self.url},
|
||||
hostname: {self.hostname},
|
||||
title: {self.title},
|
||||
description: {self.description},
|
||||
thumbnail: {self.thumbnail}
|
||||
)"""
|
|
@ -17,6 +17,7 @@ import ./dto/reaction as reaction_dto
|
|||
import ../chat/dto/chat as chat_dto
|
||||
import ./dto/pinned_message_update as pinned_msg_update_dto
|
||||
import ./dto/removed_message as removed_msg_dto
|
||||
import ./dto/link_preview
|
||||
import ./message_cursor
|
||||
|
||||
import ../../common/message as message_common
|
||||
|
@ -58,6 +59,7 @@ const SIGNAL_ENVELOPE_SENT* = "envelopeSent"
|
|||
const SIGNAL_ENVELOPE_EXPIRED* = "envelopeExpired"
|
||||
const SIGNAL_MESSAGE_LINK_PREVIEW_DATA_LOADED* = "messageLinkPreviewDataLoaded"
|
||||
const SIGNAL_RELOAD_MESSAGES* = "reloadMessages"
|
||||
const SIGNAL_URLS_UNFURLED* = "urlsUnfurled"
|
||||
|
||||
include async_tasks
|
||||
|
||||
|
@ -121,6 +123,9 @@ type
|
|||
response*: JsonNode
|
||||
uuid*: string
|
||||
|
||||
LinkPreviewV2DataArgs* = ref object of Args
|
||||
linkPreviews*: Table[string, LinkPreview]
|
||||
|
||||
ReloadMessagesArgs* = ref object of Args
|
||||
communityId*: string
|
||||
|
||||
|
@ -777,10 +782,65 @@ QtObject:
|
|||
unfurlImages: unfurlImages,
|
||||
uuid: uuid
|
||||
)
|
||||
|
||||
self.threadpool.start(arg)
|
||||
return $genOid()
|
||||
|
||||
proc getTextUrls*(self: Service, text: string): seq[string] =
|
||||
try:
|
||||
let response = status_go.getTextUrls(text)
|
||||
if response.result.kind != JArray:
|
||||
warn "expected response is not an array", methodName = "getTextUrls"
|
||||
return
|
||||
return map(response.result.getElems(), proc(x: JsonNode): string = x.getStr())
|
||||
|
||||
except Exception as e:
|
||||
error "getTextUrls failed", errName = e.name, errDesription = e.msg
|
||||
|
||||
proc onAsyncUnfurlUrlsFinished*(self: Service, response: string) {.slot.}=
|
||||
|
||||
let responseObj = response.parseJson
|
||||
if responseObj.kind != JObject:
|
||||
warn "expected response is not a json object", methodName = "onAsyncUnfurlUrlsFinished"
|
||||
return
|
||||
|
||||
let errMessage = responseObj["error"].getStr
|
||||
if errMessage != "":
|
||||
error "asyncUnfurlUrls failed", errMessage
|
||||
return
|
||||
|
||||
var requestedUrlsArr: JsonNode
|
||||
var requestedUrls: seq[string]
|
||||
if responseObj.getProp("requestedUrls", requestedUrlsArr):
|
||||
requestedUrls = map(requestedUrlsArr.getElems(), proc(x: JsonNode): string = x.getStr)
|
||||
|
||||
var linkPreviewsArr: JsonNode
|
||||
var linkPreviews: Table[string, LinkPreview]
|
||||
if responseObj.getProp("response", linkPreviewsArr):
|
||||
for element in linkPreviewsArr.getElems():
|
||||
let linkPreview = element.toLinkPreview()
|
||||
linkPreviews[linkPreview.url] = linkPreview
|
||||
|
||||
for url in requestedUrls:
|
||||
if not linkPreviews.hasKey(url):
|
||||
linkPreviews[url] = initLinkPreview(url)
|
||||
|
||||
let args = LinkPreviewV2DataArgs(
|
||||
linkPreviews: linkPreviews
|
||||
)
|
||||
self.events.emit(SIGNAL_URLS_UNFURLED, args)
|
||||
|
||||
|
||||
proc asyncUnfurlUrls*(self: Service, urls: seq[string]) =
|
||||
if len(urls) == 0:
|
||||
return
|
||||
let arg = AsyncUnfurlUrlsTaskArg(
|
||||
tptr: cast[ByteAddress](asyncUnfurlUrlsTask),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onAsyncUnfurlUrlsFinished",
|
||||
urls: urls
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
# See render-inline in status-mobile/src/status_im/ui/screens/chat/message/message.cljs
|
||||
proc renderInline(self: Service, parsedText: ParsedText, communityChats: seq[ChatDto]): string =
|
||||
let value = escape_html(parsedText.literal)
|
||||
|
|
|
@ -146,4 +146,4 @@ QtObject:
|
|||
return true
|
||||
except Exception as e:
|
||||
error "error: ", procName="validatePassword", errName = e.name, errDesription = e.msg
|
||||
return false
|
||||
return false
|
||||
|
|
|
@ -435,7 +435,8 @@ QtObject:
|
|||
replyTo,
|
||||
ContentType.Sticker.int,
|
||||
preferredUsername,
|
||||
communityId = "", # communityId is not ncessary when sending a sticker
|
||||
linkPreviews = @[],
|
||||
communityId = "", # communityId is not necessary when sending a sticker
|
||||
sticker.hash,
|
||||
sticker.packId)
|
||||
discard self.chatService.processMessageUpdateAfterSend(response)
|
||||
|
@ -456,4 +457,4 @@ QtObject:
|
|||
let network = self.networkService.getNetworkForStickers()
|
||||
|
||||
let balances = status_go_backend.getTokensBalancesForChainIDs(@[network.chainId], @[account], @[token.addressAsString()]).result
|
||||
return ens_utils.hex2Token(balances{account}{token.addressAsString()}.getStr, token.decimals)
|
||||
return ens_utils.hex2Token(balances{account}{token.addressAsString()}.getStr, token.decimals)
|
||||
|
|
|
@ -2,6 +2,7 @@ import json, sequtils, sugar, strutils
|
|||
import core, ../app_service/common/utils
|
||||
import response_type
|
||||
import interpret/cropped_image
|
||||
import ../app_service/service/message/dto/link_preview
|
||||
|
||||
export response_type
|
||||
|
||||
|
@ -60,6 +61,7 @@ proc sendChatMessage*(
|
|||
replyTo: string,
|
||||
contentType: int,
|
||||
preferredUsername: string = "",
|
||||
linkPreviews: seq[LinkPreview],
|
||||
communityId: string = "",
|
||||
stickerHash: string = "",
|
||||
stickerPack: string = "0",
|
||||
|
@ -75,7 +77,8 @@ proc sendChatMessage*(
|
|||
"pack": parseInt(stickerPack)
|
||||
},
|
||||
"contentType": contentType,
|
||||
"communityId": communityId
|
||||
"communityId": communityId,
|
||||
"linkPreviews": linkPreviews
|
||||
}
|
||||
])
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import json
|
||||
import json, strformat
|
||||
import core, ../app_service/common/utils
|
||||
import response_type
|
||||
|
||||
|
@ -72,3 +72,10 @@ proc firstUnseenMessageID*(chatId: string): RpcResponse[JsonNode] {.raises: [Exc
|
|||
let payload = %* [chatId]
|
||||
result = callPrivateRPC("firstUnseenMessageID".prefix, payload)
|
||||
|
||||
proc getTextUrls*(text: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %*[text]
|
||||
result = callPrivateRPC("getTextURLs".prefix, payload)
|
||||
|
||||
proc unfurlUrls*(urls: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %*[urls]
|
||||
result = callPrivateRPC("unfurlURLs".prefix, payload)
|
||||
|
|
|
@ -134,18 +134,20 @@ QtObject {
|
|||
return msg
|
||||
}
|
||||
|
||||
function cleanMessageText(formattedMessage) {
|
||||
const text = globalUtilsInst.plainText(StatusQUtils.Emoji.deparse(formattedMessage))
|
||||
return interpretMessage(text)
|
||||
}
|
||||
|
||||
function sendMessage(chatId, event, text, replyMessageId, fileUrlsAndSources) {
|
||||
chatCommunitySectionModule.prepareChatContentModuleForChatId(chatId)
|
||||
const chatContentModule = chatCommunitySectionModule.getChatContentModule()
|
||||
var result = false
|
||||
|
||||
let textMsg = globalUtilsInst.plainText(StatusQUtils.Emoji.deparse(text))
|
||||
const textMsg = cleanMessageText(text)
|
||||
if (textMsg.trim() !== "") {
|
||||
textMsg = interpretMessage(textMsg)
|
||||
|
||||
if (event) {
|
||||
if (event)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
if (fileUrlsAndSources.length > 0) {
|
||||
|
|
|
@ -3,6 +3,7 @@ import QtQuick.Controls 2.15
|
|||
import QtQuick.Layouts 1.15
|
||||
import QtQml 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
@ -178,7 +179,15 @@ Item {
|
|||
d.restoreInputAttachments()
|
||||
}
|
||||
|
||||
readonly property var updateLinkPreviews: {
|
||||
return Backpressure.debounce(this, 250, () => {
|
||||
const messageText = root.rootStore.cleanMessageText(chatInput.textInput.text)
|
||||
d.activeChatContentModule.inputAreaModule.setText(messageText)
|
||||
})
|
||||
}
|
||||
|
||||
onActiveChatContentModuleChanged: {
|
||||
d.activeChatContentModule.inputAreaModule.clearLinkPreviewCache()
|
||||
// Call later to make sure activeUsersStore and activeMessagesStore bindings are updated
|
||||
Qt.callLater(d.restoreInputState)
|
||||
}
|
||||
|
@ -238,6 +247,31 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// This is a non-designed preview of unfurled urls.
|
||||
// Should be replaced with a proper UI when it's ready.
|
||||
//
|
||||
// StatusListView {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.maximumHeight: 200
|
||||
// Layout.margins: Style.current.smallPadding
|
||||
|
||||
// // For a vertical list bind the imlicitHeight to contentHeight
|
||||
// implicitHeight: contentHeight
|
||||
// spacing: 10
|
||||
|
||||
// model: d.activeChatContentModule.inputAreaModule.linkPreviewModel
|
||||
|
||||
// delegate: StatusBaseText {
|
||||
// width: ListView.view.width
|
||||
// wrapMode: Text.WordWrap
|
||||
// text: {
|
||||
// const icon = unfurled ? (hostname !== "" ? '✅' : '❌') : '👀'
|
||||
// const thumbnailInfo = `thumbnail: (${thumbnailWidth}*${thumbnailHeight}, url: ${thumbnailUrl.length} symbols, data: ${thumbnailDataUri.length} symbols)`
|
||||
// return `${icon} ${url} (hostname: ${hostname}): ${title}\ndescription: ${description}\n${thumbnailInfo}`
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.current.smallPadding
|
||||
|
@ -289,6 +323,7 @@ Item {
|
|||
suggestions.suggestionFilter.addSystemSuggestions: chatType === Constants.chatType.communityChat
|
||||
|
||||
textInput.onTextChanged: {
|
||||
d.updateLinkPreviews()
|
||||
if (!!d.activeChatContentModule)
|
||||
d.activeChatContentModule.inputAreaModule.preservedProperties.text = textInput.text
|
||||
}
|
||||
|
@ -334,19 +369,19 @@ Item {
|
|||
return
|
||||
}
|
||||
|
||||
if (root.rootStore.sendMessage(d.activeChatContentModule.getMyChatId(),
|
||||
event,
|
||||
chatInput.getTextWithPublicKeys(),
|
||||
chatInput.isReply? chatInput.replyMessageId : "",
|
||||
chatInput.fileUrlsAndSources
|
||||
))
|
||||
{
|
||||
Global.playSendMessageSound()
|
||||
if (root.rootStore.sendMessage(d.activeChatContentModule.getMyChatId(),
|
||||
event,
|
||||
chatInput.getTextWithPublicKeys(),
|
||||
chatInput.isReply? chatInput.replyMessageId : "",
|
||||
chatInput.fileUrlsAndSources
|
||||
))
|
||||
{
|
||||
Global.playSendMessageSound()
|
||||
|
||||
chatInput.textInput.clear();
|
||||
chatInput.textInput.textFormat = TextEdit.PlainText;
|
||||
chatInput.textInput.textFormat = TextEdit.RichText;
|
||||
}
|
||||
chatInput.textInput.clear();
|
||||
chatInput.textInput.textFormat = TextEdit.PlainText;
|
||||
chatInput.textInput.textFormat = TextEdit.RichText;
|
||||
}
|
||||
}
|
||||
|
||||
onKeyUpPress: {
|
||||
|
|
Loading…
Reference in New Issue