Compound webpage preview

This commit is contained in:
Ali 2023-10-27 16:13:22 +04:00
parent 99a1a9392c
commit 9c75edcc31
11 changed files with 122 additions and 92 deletions

View file

@ -1308,14 +1308,14 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let anchorRange = externalUrl.range(of: "#") {
anchor = String(externalUrl[anchorRange.upperBound...])
}
strongSelf.loadWebpageDisposable.set((webpagePreviewWithProgress(account: strongSelf.context.account, url: externalUrl, webpageId: webpageId)
strongSelf.loadWebpageDisposable.set((webpagePreviewWithProgress(account: strongSelf.context.account, urls: [externalUrl], webpageId: webpageId)
|> deliverOnMainQueue).start(next: { result in
if let strongSelf = self {
switch result {
case let .result(webpage):
if let webpage = webpage, case .Loaded = webpage.content {
case let .result(webpageResult):
if let webpageResult = webpageResult, case .Loaded = webpageResult.webpage.content {
strongSelf.loadProgress.set(1.0)
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourceLocation: strongSelf.sourceLocation, anchor: anchor))
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpageResult.webpage, sourceLocation: strongSelf.sourceLocation, anchor: anchor))
}
break
case let .progress(progress):

View file

@ -343,7 +343,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
return (TelegramMediaExpiredContent(data: .file), nil, nil, nil, nil)
}
case let .messageMediaWebPage(flags, webpage):
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) {
var webpageForceLargeMedia: Bool?
if (flags & (1 << 0)) != 0 {
webpageForceLargeMedia = true

View file

@ -17,7 +17,7 @@ func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPa
}
}
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? {
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? {
switch webpage {
case .webPageNotModified:
return nil

View file

@ -994,7 +994,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
let _ = url
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
@ -1217,7 +1217,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
let _ = url
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
@ -2993,7 +2993,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo
let _ = url
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}

View file

@ -408,18 +408,18 @@ public final class AccountViewTracker {
for messageId in addedMessageIds {
if self.webpageDisposables[messageId] == nil {
if let (_, url) = localWebpages[messageId] {
self.webpageDisposables[messageId] = (webpagePreview(account: account, url: url) |> mapToSignal { result -> Signal<Void, NoError> in
guard case let .result(webpage) = result else {
self.webpageDisposables[messageId] = (webpagePreview(account: account, urls: [url]) |> mapToSignal { result -> Signal<Void, NoError> in
guard case let .result(webpageResult) = result else {
return .complete()
}
return account.postbox.transaction { transaction -> Void in
if let webpage = webpage {
if let webpageResult = webpageResult {
transaction.updateMessage(messageId, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var media = currentMessage.media
for i in 0 ..< media.count {
if let _ = media[i] as? TelegramMediaWebpage {
media[i] = webpage
media[i] = webpageResult.webpage
break
}
}

View file

@ -333,6 +333,15 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
public enum TelegramMediaWebpageContent {
case Pending(Int32, String?)
case Loaded(TelegramMediaWebpageLoadedContent)
public var url: String? {
switch self {
case let .Pending(_, value):
return value
case let .Loaded(content):
return content.url
}
}
}
public final class TelegramMediaWebpage: Media, Equatable {

View file

@ -5,13 +5,18 @@ import TelegramApi
import MtProtoKit
public enum WebpagePreviewResult : Equatable {
public enum WebpagePreviewResult: Equatable {
public struct Result: Equatable {
public var webpage: TelegramMediaWebpage
public var sourceUrl: String
}
case progress
case result(TelegramMediaWebpage?)
case result(Result?)
}
public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, url: url)
public func webpagePreview(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, urls: urls)
|> mapToSignal { next -> Signal<WebpagePreviewResult, NoError> in
if case let .result(result) = next {
return .single(.result(result))
@ -22,57 +27,69 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? =
}
public enum WebpagePreviewWithProgressResult {
case result(TelegramMediaWebpage?)
case result(WebpagePreviewResult.Result?)
case progress(Float)
}
public func webpagePreviewWithProgress(account: Account, url: String, webpageId: MediaId? = nil) -> Signal<WebpagePreviewWithProgressResult, NoError> {
public func normalizedWebpagePreviewUrl(url: String) -> String {
return url
}
public func webpagePreviewWithProgress(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewWithProgressResult, NoError> {
return account.postbox.transaction { transaction -> Signal<WebpagePreviewWithProgressResult, NoError> in
if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage {
return .single(.result(webpage))
if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage, let url = webpage.content.url {
var sourceUrl = url
if urls.count == 1 {
sourceUrl = urls[0]
}
return .single(.result(WebpagePreviewResult.Result(webpage: webpage, sourceUrl: sourceUrl)))
} else {
return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: url, entities: nil), info: .progress)
return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress)
|> `catch` { _ -> Signal<NetworkRequestResult<Api.MessageMedia>, NoError> in
return .single(.result(.messageMediaEmpty))
}
|> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> in
switch result {
case .acknowledged:
case .acknowledged:
return .complete()
case let .progress(progress, packetSize):
if packetSize > 1024 {
return .single(.progress(progress))
} else {
return .complete()
case let .progress(progress, packetSize):
if packetSize > 1024 {
return .single(.progress(progress))
} else {
return .complete()
}
case let .result(result):
if let preCachedResources = result.preCachedResources {
for (resource, data) in preCachedResources {
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
}
case let .result(result):
if let preCachedResources = result.preCachedResources {
for (resource, data) in preCachedResources {
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
}
}
switch result {
case let .messageMediaWebPage(flags, webpage):
let _ = flags
if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) {
if case .Loaded = media.content {
return .single(.result(media))
} else {
return .single(.result(media))
|> then(
account.stateManager.updatedWebpage(media.webpageId)
|> take(1)
|> map { next -> WebpagePreviewWithProgressResult in
return .result(next)
}
)
}
switch result {
case let .messageMediaWebPage(flags, webpage):
let _ = flags
if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url {
if case .Loaded = media.content {
return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
} else {
return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
|> then(
account.stateManager.updatedWebpage(media.webpageId)
|> take(1)
|> map { next -> WebpagePreviewWithProgressResult in
if let url = next.content.url {
return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url))
} else {
return .result(nil)
}
}
} else {
return .single(.result(nil))
}
default:
return .single(.result(nil))
)
}
} else {
return .single(.result(nil))
}
default:
return .single(.result(nil))
}
}
}
}
@ -95,7 +112,7 @@ public func actualizedWebpage(account: Account, webpage: TelegramMediaWebpage) -
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(apiWebpage), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
return .single(updatedWebpage)
} else if case let .webPageNotModified(_, viewsValue) = apiWebpage, let views = viewsValue, case let .Loaded(content) = webpage.content {
let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(
@ -143,7 +160,7 @@ func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: Eng
return .single(nil)
}
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId.id == id {
if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId.id == id {
return postbox.transaction { transaction -> TelegramMediaWebpage? in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)

View file

@ -837,14 +837,14 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
return
}
if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState {
if let webpage = webpageCache[updatedUrlPreviewState.detectedUrl] {
if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState, let detectedUrl = updatedUrlPreviewState.detectedUrls.first {
if let webpage = webpageCache[detectedUrl] {
progress?.set(.single(false))
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
if state.interfaceState.editMessage != nil {
if var urlPreview = state.editingUrlPreview {
urlPreview.url = updatedUrlPreviewState.detectedUrl
urlPreview.url = detectedUrl
urlPreview.webPage = webpage
return state.updatedEditingUrlPreview(urlPreview)
@ -853,7 +853,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
}
} else {
if var urlPreview = state.urlPreview {
urlPreview.url = updatedUrlPreviewState.detectedUrl
urlPreview.url = detectedUrl
urlPreview.webPage = webpage
return state.updatedUrlPreview(urlPreview)
@ -875,20 +875,20 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
if state.interfaceState.editMessage != nil {
if let webpage = result(nil), var urlPreview = state.editingUrlPreview {
urlPreview.url = updatedUrlPreviewState.detectedUrl
if let (webpage, webpageUrl) = result(nil), var urlPreview = state.editingUrlPreview {
urlPreview.url = webpageUrl
urlPreview.webPage = webpage
webpageCache[updatedUrlPreviewState.detectedUrl] = webpage
webpageCache[detectedUrl] = webpage
return state.updatedEditingUrlPreview(urlPreview)
} else {
return state
}
} else {
if let webpage = result(nil), var urlPreview = state.urlPreview {
urlPreview.url = updatedUrlPreviewState.detectedUrl
if let (webpage, webpageUrl) = result(nil), var urlPreview = state.urlPreview {
urlPreview.url = webpageUrl
urlPreview.webPage = webpage
webpageCache[updatedUrlPreviewState.detectedUrl] = webpage
webpageCache[detectedUrl] = webpage
return state.updatedUrlPreview(urlPreview)
} else {

View file

@ -223,7 +223,7 @@ func updateChatPresentationInterfaceStateImpl(
if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0) {
selfController.urlPreviewQueryState?.1.dispose()
var inScope = true
var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)?
var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)?
let linkPreviews: Signal<Bool, NoError>
if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
linkPreviews = interactiveChatLinkPreviewsEnabled(accountManager: selfController.context.sharedContext.accountManager, displayAlert: { [weak selfController] f in
@ -252,7 +252,7 @@ func updateChatPresentationInterfaceStateImpl(
}
let filteredPreviewSignal = linkPreviews
|> take(1)
|> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError> in
|> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError> in
if value {
return updatedUrlPreviewSignal
} else {
@ -260,7 +260,7 @@ func updateChatPresentationInterfaceStateImpl(
}
}
selfController.urlPreviewQueryState = (updatedUrlPreviewState, (filteredPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] (result) in
selfController.urlPreviewQueryState = (updatedUrlPreviewState, (filteredPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in
guard let selfController else {
return
}
@ -269,9 +269,9 @@ func updateChatPresentationInterfaceStateImpl(
inScopeResult = result
} else {
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, {
if let updatedUrlPreviewState, let webpage = result($0.urlPreview?.webPage) {
if let (webpage, webpageUrl) = result($0.urlPreview?.webPage) {
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
url: updatedUrlPreviewState.detectedUrl,
url: webpageUrl,
webPage: webpage,
positionBelowText: $0.urlPreview?.positionBelowText ?? true,
largeMedia: $0.urlPreview?.largeMedia
@ -285,9 +285,9 @@ func updateChatPresentationInterfaceStateImpl(
}))
inScope = false
if let inScopeResult = inScopeResult {
if let updatedUrlPreviewState, let webpage = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) {
if let (webpage, webpageUrl) = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) {
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
url: updatedUrlPreviewState.detectedUrl,
url: webpageUrl,
webPage: webpage,
positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true,
largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia
@ -304,7 +304,7 @@ func updateChatPresentationInterfaceStateImpl(
if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0) {
selfController.editingUrlPreviewQueryState?.1.dispose()
var inScope = true
var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)?
var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)?
selfController.editingUrlPreviewQueryState = (updatedEditingUrlPreviewState, (updatedEditingUrlPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in
guard let selfController else {
return
@ -314,9 +314,9 @@ func updateChatPresentationInterfaceStateImpl(
inScopeResult = result
} else {
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, {
if let updatedEditingUrlPreviewState, let webpage = result($0.editingUrlPreview?.webPage) {
if let (webpage, webpageUrl) = result($0.editingUrlPreview?.webPage) {
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
url: updatedEditingUrlPreviewState.detectedUrl,
url: webpageUrl,
webPage: webpage,
positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true,
largeMedia: $0.editingUrlPreview?.largeMedia
@ -330,9 +330,9 @@ func updateChatPresentationInterfaceStateImpl(
}))
inScope = false
if let inScopeResult = inScopeResult {
if let updatedEditingUrlPreviewState, let webpage = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) {
if let (webpage, webpageUrl) = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) {
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
url: updatedEditingUrlPreviewState.detectedUrl,
url: webpageUrl,
webPage: webpage,
positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true,
largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia

View file

@ -507,10 +507,10 @@ func detectUrls(_ inputText: NSAttributedString?) -> [String] {
}
struct UrlPreviewState {
var detectedUrl: String
var detectedUrls: [String]
}
func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? {
func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError>)? {
guard let _ = inputText else {
if currentQuery != nil {
return (nil, .single({ _ in return nil }))
@ -519,15 +519,19 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco
}
}
if let _ = dataDetector {
let detectedUrl = detectUrls(inputText).first
if detectedUrl != currentQuery?.detectedUrl {
if let detectedUrl = detectedUrl {
return (UrlPreviewState(detectedUrl: detectedUrl), webpagePreview(account: context.account, url: detectedUrl)
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
guard case let .result(result) = result else {
let detectedUrls = detectUrls(inputText)
if detectedUrls != currentQuery?.detectedUrls {
if !detectedUrls.isEmpty {
return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls)
|> mapToSignal { result -> Signal<(TelegramMediaWebpage, String)?, NoError> in
guard case let .result(webpageResult) = result else {
return .complete()
}
return .single(result)
if let webpageResult {
return .single((webpageResult.webpage, webpageResult.sourceUrl))
} else {
return .single(nil)
}
}
|> map { value in
return { _ in return value }

View file

@ -1157,14 +1157,14 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String
}
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolveUrlResult, NoError> {
return webpagePreview(account: account, url: url)
return webpagePreview(account: account, urls: [url])
|> mapToSignal { result -> Signal<ResolveUrlResult, NoError> in
switch result {
case .progress:
return .single(.progress)
case let .result(webpage):
if let webpage = webpage {
if case let .Loaded(content) = webpage.content {
case let .result(webpageResult):
if let webpageResult = webpageResult {
if case let .Loaded(content) = webpageResult.webpage.content {
if content.instantPage != nil {
var anchorValue: String?
if let anchorRange = url.range(of: "#") {
@ -1173,7 +1173,7 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal<Resol
anchorValue = String(anchor)
}
}
return .single(.result(.instantView(webpage, anchorValue)))
return .single(.result(.instantView(webpageResult.webpage, anchorValue)))
} else {
return .single(.result(.externalUrl(url)))
}