no message

This commit is contained in:
Peter 2017-03-03 13:43:26 +04:00
parent 9523bfa138
commit 5bd28cbc65
18 changed files with 506 additions and 51 deletions

View File

@ -203,6 +203,12 @@
D050F26E1E4A5B6D00988324 /* RemovePeerChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38741E40A7F70044D6FE /* RemovePeerChat.swift */; };
D0528E5A1E658B3600E2FEF5 /* ManagedLocalInputActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */; };
D0528E5B1E658B3600E2FEF5 /* ManagedLocalInputActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */; };
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */; };
D0528E611E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */; };
D0528E651E65C82400E2FEF5 /* UpdateContactName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */; };
D0528E661E65C82400E2FEF5 /* UpdateContactName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */; };
D0528E6A1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */; };
D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */; };
D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */; };
D0561DE41E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */; };
D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */; };
@ -533,6 +539,9 @@
D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractivePhoneFormatter.swift; sourceTree = "<group>"; };
D050F2501E4A59C200988324 /* JoinLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLink.swift; sourceTree = "<group>"; };
D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedLocalInputActivities.swift; sourceTree = "<group>"; };
D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleMessageView.swift; sourceTree = "<group>"; };
D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateContactName.swift; sourceTree = "<group>"; };
D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebpagePreview.swift; sourceTree = "<group>"; };
D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerInfo.swift; sourceTree = "<group>"; };
D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = "<group>"; };
D0613FC91E60440600202CDB /* InvitationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitationLinks.swift; sourceTree = "<group>"; };
@ -963,6 +972,8 @@
D0AAD1A91E32638500D5B9DE /* ApplyMaxReadIndexInteractively.swift */,
D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */,
C239BE961E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift */,
D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */,
D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */,
);
name = Messages;
sourceTree = "<group>";
@ -1105,6 +1116,7 @@
D033FEB21E61F3C000644997 /* ReportPeer.swift */,
D033FEB51E61F3F900644997 /* BlockedPeers.swift */,
C239BE9B1E630CA700C2C453 /* UpdatePinnedMessage.swift */,
D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */,
);
name = Peers;
sourceTree = "<group>";
@ -1427,6 +1439,7 @@
D0DC354E1DE368F7000195EB /* RequestChatContextResults.swift in Sources */,
D0BC38771E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift in Sources */,
D0B843851DA6EDC4005F29E1 /* CachedChannelData.swift in Sources */,
D0528E6A1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */,
D0BEAF5D1E54941B00BD963D /* Authorization.swift in Sources */,
D0B843831DA6EDB8005F29E1 /* CachedGroupData.swift in Sources */,
D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
@ -1469,6 +1482,7 @@
D03B0D5B1D631A6900955575 /* Buffer.swift in Sources */,
D0B843891DA7AB96005F29E1 /* ExportedInvitation.swift in Sources */,
D03B0E441D631E6600955575 /* NetworkLogging.m in Sources */,
D0528E651E65C82400E2FEF5 /* UpdateContactName.swift in Sources */,
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */,
D03B0CBB1D62233C00955575 /* MergeLists.swift in Sources */,
C239BE971E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift in Sources */,
@ -1476,6 +1490,7 @@
D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */,
D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */,
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
D0528E5A1E658B3600E2FEF5 /* ManagedLocalInputActivities.swift in Sources */,
D0FA8BA41E1FA341001E855B /* SecretChatKeychain.swift in Sources */,
D01749601E118FC30057C89A /* AccountIntermediateState.swift in Sources */,
@ -1623,6 +1638,7 @@
D00C7CCD1E3620C30080C3D5 /* CachedChannelParticipants.swift in Sources */,
D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */,
D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */,
D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */,
D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */,
D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */,
@ -1665,6 +1681,7 @@
D0448CA61E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */,
D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */,
D050F2641E4A5AEB00988324 /* ManagedSynchronizePinnedChatsOperations.swift in Sources */,
D0528E661E65C82400E2FEF5 /* UpdateContactName.swift in Sources */,
D0F7B1E81E045C87007EB8A5 /* PeerParticipants.swift in Sources */,
D049EAD61E43D98500A2CD3A /* RecentMediaItem.swift in Sources */,
D0B844331DAB91E0005F29E1 /* NBPhoneNumber.m in Sources */,
@ -1672,6 +1689,7 @@
D001F3F61E128A1C007A8C60 /* PendingMessageUploadedContent.swift in Sources */,
C2366C871E4F403C0097CCFF /* AddressNames.swift in Sources */,
D02ABC7F1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */,
D0528E611E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
D0528E5B1E658B3600E2FEF5 /* ManagedLocalInputActivities.swift in Sources */,
D0FA8BA51E1FA341001E855B /* SecretChatKeychain.swift in Sources */,
D0F7B1E71E045C87007EB8A5 /* JoinChannel.swift in Sources */,

View File

@ -376,6 +376,8 @@ public class Account {
return self.networkStateValue.get()
}
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId) {
self.id = id
self.basePath = basePath
@ -594,7 +596,10 @@ public class Account {
}
}
public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: ((_ account: Account, _ resource: MediaResource, _ resourceData: MediaResourceData, _ representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError>)? = nil) {
public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ resourceData: MediaResourceData, _ representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError>
public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: Media, _ userInteractive: Bool) -> Signal<Media?, NoError>
public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) {
account.postbox.mediaBox.fetchResource = { [weak account] resource, range -> Signal<MediaResourceDataFetchResult, NoError> in
if let strongAccount = account {
return fetchResource(account: strongAccount, resource: resource, range: range)
@ -611,6 +616,9 @@ public func setupAccount(_ account: Account, fetchCachedResourceRepresentation:
}
}
account.transformOutgoingMessageMedia = transformOutgoingMessageMedia
account.pendingMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia
account.managedContactsDisposable.set(manageContacts(network: account.network, postbox: account.postbox).start())
account.managedStickerPacksDisposable.set(manageStickerPacks(network: account.network, postbox: account.postbox).start())

View File

@ -42,7 +42,7 @@ public func checkAddressNameFormat(_ value: String) -> AddressNameFormatError? {
if index == 0 && char >= "0" && char <= "9" {
return .startsWithDigit
}
if (!((char >= "a" && char <= "z") || (char >= "A" && char <= "Z") || (char >= "0" && char <= "9"))) {
if (!((char >= "a" && char <= "z") || (char >= "A" && char <= "Z") || (char >= "0" && char <= "9") || char == "_")) {
return .invalidCharacters
}
index += 1

View File

@ -173,7 +173,7 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource {
public func isEqual(to: TelegramMediaResource) -> Bool {
if let to = to as? CloudDocumentMediaResource {
return self.datacenterId != to.datacenterId && self.fileId != to.fileId && self.accessHash != to.accessHash && self.size != to.size
return self.datacenterId == to.datacenterId && self.fileId == to.fileId && self.accessHash == to.accessHash && self.size == to.size
} else {
return false
}

View File

@ -24,15 +24,15 @@ public enum EnqueueMessage {
private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAttribute]) -> [MessageAttribute] {
return attributes.filter { attribute in
switch attribute {
case let _ as TextEntitiesMessageAttribute:
case _ as TextEntitiesMessageAttribute:
return true
case let _ as InlineBotMessageAttribute:
case _ as InlineBotMessageAttribute:
return true
case let _ as OutgoingMessageInfoAttribute:
case _ as OutgoingMessageInfoAttribute:
return true
case let _ as ReplyMarkupMessageAttribute:
case _ as ReplyMarkupMessageAttribute:
return true
case let _ as OutgoingChatContextResultMessageAttribute:
case _ as OutgoingChatContextResultMessageAttribute:
return true
default:
return false
@ -43,9 +43,9 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt
private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAttribute]) -> [MessageAttribute] {
return attributes.filter { attribute in
switch attribute {
case let _ as TextEntitiesMessageAttribute:
case _ as TextEntitiesMessageAttribute:
return true
case let _ as InlineBotMessageAttribute:
case _ as InlineBotMessageAttribute:
return true
default:
return false
@ -53,25 +53,83 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt
}
}
func opportunisticallyTransformMessageWithMedia(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia, media: Media, userInteractive: Bool) -> Signal<Media?, NoError> {
return transformOutgoingMessageMedia(postbox, network, media, userInteractive)
|> timeout(2.0, queue: Queue.concurrentDefaultQueue(), alternate: .single(nil))
}
private func opportunisticallyTransformOutgoingMedia(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia, messages: [EnqueueMessage], userInteractive: Bool) -> Signal<[(Bool, EnqueueMessage)], NoError> {
var hasMedia = false
loop: for message in messages {
switch message {
case let .message(_, _, media, _):
if media != nil {
hasMedia = true
break loop
}
case .forward:
break
}
}
if !hasMedia {
return .single(messages.map { (true, $0) })
}
var signals: [Signal<(Bool, EnqueueMessage), NoError>] = []
for message in messages {
switch message {
case let .message(text, attributes, media, replyToMessageId):
if let media = media {
signals.append(opportunisticallyTransformMessageWithMedia(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, media: media, userInteractive: userInteractive) |> map { result -> (Bool, EnqueueMessage) in
if let result = result {
return (true, .message(text: text, attributes: attributes, media: result, replyToMessageId: replyToMessageId))
} else {
return (false, .message(text: text, attributes: attributes, media: media, replyToMessageId: replyToMessageId))
}
})
} else {
signals.append(.single((false, message)))
}
case .forward:
signals.append(.single((false, message)))
}
}
return combineLatest(signals)
}
public func enqueueMessages(account: Account, peerId: PeerId, messages: [EnqueueMessage]) -> Signal<[MessageId?], NoError> {
return account.postbox.modify { modifier -> [MessageId?] in
return enqueueMessages(modifier: modifier, account: account, peerId: peerId, messages: messages)
let signal: Signal<[(Bool, EnqueueMessage)], NoError>
if let transformOutgoingMessageMedia = account.transformOutgoingMessageMedia {
signal = opportunisticallyTransformOutgoingMedia(network: account.network, postbox: account.postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messages: messages, userInteractive: true)
} else {
signal = .single(messages.map { (false, $0) })
}
return signal
|> mapToSignal { messages -> Signal<[MessageId?], NoError> in
return account.postbox.modify { modifier -> [MessageId?] in
return enqueueMessages(modifier: modifier, account: account, peerId: peerId, messages: messages)
}
}
}
func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messages: [EnqueueMessage]) -> [MessageId?] {
func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messages: [(Bool, EnqueueMessage)]) -> [MessageId?] {
if let peer = modifier.getPeer(peerId) {
var storeMessages: [StoreMessage] = []
let timestamp = Int32(account.network.context.globalTime())
var globallyUniqueIds: [Int64] = []
for message in messages {
for (transformedMedia, message) in messages {
var attributes: [MessageAttribute] = []
var flags = StoreMessageFlags()
flags.insert(.Unsent)
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId))
var infoFlags = OutgoingMessageInfoFlags()
if transformedMedia {
infoFlags.insert(.transformedMedia)
}
attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId, flags: infoFlags))
globallyUniqueIds.append(randomId)
switch message {

View File

@ -16,7 +16,6 @@ private func fetchCloudMediaLocation(account: Account, resource: TelegramCloudMe
#if os(iOS)
private func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, NoError> {
return Signal { subscriber in
let options = PHFetchOptions()
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
var requestId: PHImageRequestID?
if fetchResult.count != 0 {
@ -63,6 +62,8 @@ private func fetchLocalFileResource(path: String) -> Signal<MediaResourceDataFet
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
subscriber.putNext(MediaResourceDataFetchResult(data: data, complete: true))
subscriber.putCompletion()
} else {
subscriber.putNext(MediaResourceDataFetchResult(data: Data(), complete: false))
}
return EmptyDisposable
}
@ -70,21 +71,25 @@ private func fetchLocalFileResource(path: String) -> Signal<MediaResourceDataFet
func fetchResource(account: Account, resource: MediaResource, range: Range<Int>) -> Signal<MediaResourceDataFetchResult, NoError> {
if let _ = resource as? EmptyMediaResource {
return .never()
return .single(MediaResourceDataFetchResult(data: Data(), complete: false))
} else if let secretFileResource = resource as? SecretFileMediaResource {
return fetchSecretFileResource(account: account, resource: secretFileResource, range: range)
return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, range: range))
} else if let cloudResource = resource as? TelegramCloudMediaResource {
return fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range)
return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range))
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
#if os(iOS)
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)
return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier))
#else
return .never()
return .single(MediaResourceDataFetchResult(data: Data(), complete: false))
#endif
} else if let localFileResource = resource as? LocalFileReferenceMediaResource {
return fetchLocalFileResource(path: localFileResource.localFilePath)
if false {
//return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchLocalFileResource(path: localFileResource.localFilePath) |> delay(10.0, queue: Queue.concurrentDefaultQueue()))
} else {
return fetchLocalFileResource(path: localFileResource.localFilePath)
}
} else if let httpReference = resource as? HttpReferenceMediaResource {
return fetchHttpResource(url: httpReference.url)
return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchHttpResource(url: httpReference.url))
}
return .never()
return .single(MediaResourceDataFetchResult(data: Data(), complete: false))
}

View File

@ -133,8 +133,8 @@ func managedSecretChatOutgoingOperations(postbox: Postbox, network: Network) ->
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .noop(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds):
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .readMessageContents(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout):
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, timeout: timeout), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout,messageId):
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, timeout: timeout, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .resendOperations(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, fromSeqNo: fromSeqNo, toSeqNo: toSeqNo), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
@ -339,7 +339,7 @@ private enum SecretMessageAction {
case pfsCommitKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, keyFingerprint: Int64)
case noop(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64)
case readMessageContents(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
case setMessageAutoremoveTimeout(layer: SecretChatLayer, actionGloballyUniqueId: Int64, timeout: Int32)
case setMessageAutoremoveTimeout(layer: SecretChatLayer, actionGloballyUniqueId: Int64, timeout: Int32, messageId: MessageId)
var globallyUniqueId: Int64 {
switch self {
@ -365,10 +365,19 @@ private enum SecretMessageAction {
return actionGloballyUniqueId
case let .readMessageContents(_, actionGloballyUniqueId, _):
return actionGloballyUniqueId
case let .setMessageAutoremoveTimeout(_, actionGloballyUniqueId, _):
case let .setMessageAutoremoveTimeout(_, actionGloballyUniqueId, _, _):
return actionGloballyUniqueId
}
}
var messageId: MessageId? {
switch self {
case let .setMessageAutoremoveTimeout(_, _, _, messageId):
return messageId
default:
return nil
}
}
}
private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute]) -> [SecretApi46.DocumentAttribute] {
@ -580,7 +589,7 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
}
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout):
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
switch layer {
case .layer8:
let randomBytesData = malloc(15)!
@ -670,6 +679,29 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|> mapToSignal { result in
return postbox.modify { modifier -> Void in
markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex)
if let messageId = action.messageId {
modifier.updateMessage(messageId, update: { currentMessage in
var flags = StoreMessageFlags(currentMessage.flags)
var timestamp = currentMessage.timestamp
if let result = result {
switch result {
case let .sentEncryptedMessage(date):
timestamp = date
case let .sentEncryptedFile(date, _):
timestamp = date
}
flags.remove(.Unsent)
flags.remove(.Sending)
} else {
flags = [.Failed]
}
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date)
}
return StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, timestamp: timestamp, flags: flags, tags: currentMessage.tags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)
})
}
}
}
} else {

View File

@ -157,7 +157,7 @@ private final class MultipartFetchManager {
data = data.subdata(in: 0 ..< partSize)
}
strongSelf.receivedSize += data.count
if let completeSize = strongSelf.completeSize {
if let _ = strongSelf.completeSize {
if data.count != partSize {
assertionFailure()
return

View File

@ -262,7 +262,7 @@ func multipartUpload(network: Network, postbox: Postbox, resource: MediaResource
encryptionKey = SecretFileEncryptionKey(aesKey: aesKey, aesIv: aesIv)
}
let resourceData = postbox.mediaBox.resourceData(resource, complete: true)
let resourceData = postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: false))
let manager = MultipartUploadManager(data: resourceData, encryptionKey: encryptionKey, uploadPart: { part in
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data)

View File

@ -5,18 +5,40 @@ import Foundation
import Postbox
#endif
public struct OutgoingMessageInfoFlags: OptionSet {
public var rawValue: Int32
public init() {
self.rawValue = 0
}
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static var transformedMedia = OutgoingMessageInfoFlags(rawValue: 1 << 0)
}
public class OutgoingMessageInfoAttribute: MessageAttribute {
public let uniqueId: Int64
public let flags: OutgoingMessageInfoFlags
init(uniqueId: Int64) {
init(uniqueId: Int64, flags: OutgoingMessageInfoFlags) {
self.uniqueId = uniqueId
self.flags = flags
}
required public init(decoder: Decoder) {
self.uniqueId = decoder.decodeInt64ForKey("u")
self.flags = OutgoingMessageInfoFlags(rawValue: decoder.decodeInt32ForKey("f"))
}
public func encode(_ encoder: Encoder) {
encoder.encodeInt64(self.uniqueId, forKey: "u")
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
}
public func withUpdatedFlags(_ flags: OutgoingMessageInfoFlags) -> OutgoingMessageInfoAttribute {
return OutgoingMessageInfoAttribute(uniqueId: self.uniqueId, flags: flags)
}
}

View File

@ -57,6 +57,8 @@ public final class PendingMessageManager {
private var peerSummaryContexts: [PeerId: PeerPendingMessagesSummaryContext] = [:]
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
init(network: Network, postbox: Postbox, stateManager: AccountStateManager) {
self.network = network
self.postbox = postbox
@ -170,7 +172,7 @@ public final class PendingMessageManager {
continue
}
let uploadedContent = uploadedMessageContent(network: strongSelf.network, postbox: strongSelf.postbox, message: message)
let uploadedContent = uploadedMessageContent(network: strongSelf.network, postbox: strongSelf.postbox, transformOutgoingMessageMedia: strongSelf.transformOutgoingMessageMedia, message: message)
let sendMessage = uploadedContent
|> mapToSignal { contentResult -> Signal<PendingMessageResult, NoError> in
@ -255,13 +257,15 @@ public final class PendingMessageManager {
if let media = media as? TelegramMediaAction {
if case let .messageAutoremoveTimeoutUpdated(value) = media.action {
sentAsAction = true
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value), state: state)
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state)
if updatedState != state {
modifier.setPeerChatState(message.id.peerId, state: updatedState)
}
modifier.updateMessage(message.id, update: { currentMessage in
var flags = StoreMessageFlags(message.flags)
flags.remove(.Sending)
if !flags.contains(.Failed) {
flags.insert(.Sending)
}
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date)

View File

@ -20,7 +20,7 @@ enum PendingMessageUploadedContentResult {
case content(Message, PendingMessageUploadedContent)
}
func uploadedMessageContent(network: Network, postbox: Postbox, message: Message) -> Signal<PendingMessageUploadedContentResult, NoError> {
func uploadedMessageContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, message: Message) -> Signal<PendingMessageUploadedContentResult, NoError> {
var outgoingChatContextResultAttribute: OutgoingChatContextResultMessageAttribute?
for attribute in message.attributes {
if let attribute = attribute as? OutgoingChatContextResultMessageAttribute {
@ -28,7 +28,7 @@ func uploadedMessageContent(network: Network, postbox: Postbox, message: Message
}
}
if let forwardInfo = message.forwardInfo {
if let _ = message.forwardInfo {
var forwardSourceInfo: ForwardSourceInfoAttribute?
for attribute in message.attributes {
if let attribute = attribute as? ForwardSourceInfoAttribute {
@ -44,13 +44,13 @@ func uploadedMessageContent(network: Network, postbox: Postbox, message: Message
} else if let outgoingChatContextResultAttribute = outgoingChatContextResultAttribute {
return .single(.content(message, .chatContextResult(outgoingChatContextResultAttribute)))
} else if let media = message.media.first {
if let image = media as? TelegramMediaImage, let largestRepresentation = largestImageRepresentation(image.representations) {
if let image = media as? TelegramMediaImage, let _ = largestImageRepresentation(image.representations) {
return uploadedMediaImageContent(network: network, postbox: postbox, image: image, message: message)
} else if let file = media as? TelegramMediaFile {
if let resource = file.resource as? CloudDocumentMediaResource {
return .single(.content(message, .media(Api.InputMedia.inputMediaDocument(id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash), caption: message.text))))
} else {
return uploadedMediaFileContent(network: network, postbox: postbox, file: file, message: message)
return uploadedMediaFileContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, file: file, message: message)
}
} else if let contact = media as? TelegramMediaContact {
let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName)
@ -129,9 +129,33 @@ private func inputDocumentAttributesFromFile(_ file: TelegramMediaFile) -> [Api.
return attributes
}
private func uploadedMediaFileContent(network: Network, postbox: Postbox, file: TelegramMediaFile, message: Message) -> Signal<PendingMessageUploadedContentResult, NoError> {
return multipartUpload(network: network, postbox: postbox, resource: file.resource, encrypt: message.id.peerId.namespace == Namespaces.Peer.SecretChat)
|> map { next -> PendingMessageUploadedContentResult in
private enum UploadedMediaTransform {
case pending
case done(Media?)
}
private enum UploadedMediaThumbnail {
case pending
case done(Api.InputFile?)
}
private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal<Api.InputFile?, NoError> {
return multipartUpload(network: network, postbox: postbox, resource: image.resource, encrypt: false)
|> mapToSignal { result -> Signal<Api.InputFile?, NoError> in
switch result {
case .progress:
return .complete()
case let .inputFile(inputFile):
return .single(inputFile)
case let .inputSecretFile(file, size, key):
return .single(nil)
}
}
}
private func uploadedMediaFileContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, file: TelegramMediaFile, message: Message) -> Signal<PendingMessageUploadedContentResult, NoError> {
let upload = multipartUpload(network: network, postbox: postbox, resource: file.resource, encrypt: message.id.peerId.namespace == Namespaces.Peer.SecretChat)
/*|> map { next -> UploadedMediaFileContent in
switch next {
case let .progress(progress):
return .progress(progress)
@ -140,5 +164,90 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, file:
case let .inputSecretFile(file, size, key):
return .content(message, .secretMedia(file, size, key))
}
}*/
var alreadyTransformed = false
for attribute in message.attributes {
if let attribute = attribute as? OutgoingMessageInfoAttribute {
if attribute.flags.contains(.transformedMedia) {
alreadyTransformed = true
}
break
}
}
let transform: Signal<UploadedMediaTransform, Void>
if let transformOutgoingMessageMedia = transformOutgoingMessageMedia, !alreadyTransformed {
transform = .single(.pending) |> then(transformOutgoingMessageMedia(postbox, network, file, false)
|> mapToSignal { media -> Signal<UploadedMediaTransform, NoError> in
return postbox.modify { modifier -> UploadedMediaTransform in
if let media = media {
if let id = media.id {
modifier.updateMedia(id, update: media)
modifier.updateMessage(message.id, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date)
}
var updatedAttributes = currentMessage.attributes
if let index = updatedAttributes.index(where: { $0 is OutgoingMessageInfoAttribute }){
let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
} else {
updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia]))
}
return StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)
})
}
return .done(media)
} else {
return .done(file)
}
}
})
} else {
transform = .single(.done(file))
}
let thumbnail: Signal<UploadedMediaThumbnail, NoError> = .single(.pending) |> then(transform
|> mapToSignal { media -> Signal<UploadedMediaThumbnail, NoError> in
switch media {
case .pending:
return .single(.pending)
case let .done(media):
if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) {
return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail)
|> map { result in
return .done(result)
}
} else {
return .single(.done(nil))
}
}
})
return combineLatest(upload, thumbnail)
|> mapToSignal { content, media -> Signal<PendingMessageUploadedContentResult, NoError> in
switch content {
case let .progress(progress):
return .single(.progress(progress))
case let .inputFile(inputFile):
if case let .done(thumbnail) = media {
let inputMedia: Api.InputMedia
if let thumbnail = thumbnail {
inputMedia = Api.InputMedia.inputMediaUploadedThumbDocument(flags: 0, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFile(file), caption: message.text, stickers: nil)
} else {
inputMedia = Api.InputMedia.inputMediaUploadedDocument(flags: 0, file: inputFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFile(file), caption: message.text, stickers: nil)
}
return .single(.content(message, .media(inputMedia)))
} else {
return .complete()
}
case let .inputSecretFile(file, size, key):
if case .done = media {
return .single(.content(message, .secretMedia(file, size, key)))
} else {
return .complete()
}
}
}
}

View File

@ -112,7 +112,7 @@ enum SecretChatOutgoingOperationContents: Coding {
case pfsAbortSession(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64)
case pfsCommitKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, keyFingerprint: Int64)
case noop(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64)
case setMessageAutoremoveTimeout(layer: SecretChatLayer, actionGloballyUniqueId: Int64, timeout: Int32)
case setMessageAutoremoveTimeout(layer: SecretChatLayer, actionGloballyUniqueId: Int64, timeout: Int32, messageId: MessageId)
case terminate
init(decoder: Decoder) {
@ -144,7 +144,7 @@ enum SecretChatOutgoingOperationContents: Coding {
case SecretChatOutgoingOperationValue.noop.rawValue:
self = .noop(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l"))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i"))
case SecretChatOutgoingOperationValue.setMessageAutoremoveTimeout.rawValue:
self = .setMessageAutoremoveTimeout(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l"))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i"), timeout: decoder.decodeInt32ForKey("t"))
self = .setMessageAutoremoveTimeout(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l"))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i"), timeout: decoder.decodeInt32ForKey("t"), messageId: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p")), namespace: decoder.decodeInt32ForKey("m.n"), id: decoder.decodeInt32ForKey("m.i")))
case SecretChatOutgoingOperationValue.terminate.rawValue:
self = .terminate
default:
@ -229,11 +229,14 @@ enum SecretChatOutgoingOperationContents: Coding {
encoder.encodeInt32(SecretChatOutgoingOperationValue.noop.rawValue, forKey: "r")
encoder.encodeInt32(layer.rawValue, forKey: "l")
encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout):
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, messageId):
encoder.encodeInt32(SecretChatOutgoingOperationValue.setMessageAutoremoveTimeout.rawValue, forKey: "r")
encoder.encodeInt32(layer.rawValue, forKey: "l")
encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
encoder.encodeInt32(timeout, forKey: "t")
encoder.encodeInt64(messageId.peerId.toInt64(), forKey: "m.p")
encoder.encodeInt32(messageId.namespace, forKey: "m.n")
encoder.encodeInt32(messageId.id, forKey: "m.i")
case .terminate:
encoder.encodeInt32(SecretChatOutgoingOperationValue.terminate.rawValue, forKey: "r")
}

View File

@ -19,7 +19,7 @@ public func setSecretChatMessageAutoremoveTimeoutInteractively(account: Account,
modifier.setPeerChatState(peerId, state: updatedState)
}
enqueueMessages(modifier: modifier, account: account, peerId: peerId, messages: [.message(text: "", attributes: [], media: TelegramMediaAction(action: TelegramMediaActionType.messageAutoremoveTimeoutUpdated(timeout == nil ? 0 : timeout!)), replyToMessageId: nil)])
enqueueMessages(modifier: modifier, account: account, peerId: peerId, messages: [(true, .message(text: "", attributes: [], media: TelegramMediaAction(action: TelegramMediaActionType.messageAutoremoveTimeoutUpdated(timeout == nil ? 0 : timeout!)), replyToMessageId: nil))])
}
}
}

View File

@ -0,0 +1,100 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public func singleMessageView(account: Account, messageId: MessageId, loadIfNotExists: Bool) -> Signal<MessageView, NoError> {
return Signal { subscriber in
let loadedMessage = account.postbox.modify { modifier -> Signal<Void, NoError> in
if modifier.getMessage(messageId) == nil, loadIfNotExists {
return fetchMessage(modifier: modifier, account: account, messageId: messageId)
} else {
return .complete()
}
} |> switchToLatest
let disposable = loadedMessage.start()
let viewDisposable = account.postbox.messageView(messageId).start(next: { view in
subscriber.putNext(view)
})
return ActionDisposable {
disposable.dispose()
viewDisposable.dispose()
}
}
}
private func fetchMessage(modifier: Modifier, account: Account, messageId: MessageId) -> Signal<Void, NoError> {
if let peer = modifier.getPeer(messageId.peerId) {
var signal: Signal<Api.messages.Messages, MTRpcError>?
if messageId.peerId.namespace == Namespaces.Peer.CloudUser || messageId.peerId.namespace == Namespaces.Peer.CloudGroup {
signal = account.network.request(Api.functions.messages.getMessages(id: [messageId.id]))
} else if messageId.peerId.namespace == Namespaces.Peer.CloudChannel {
if let inputChannel = apiInputChannel(peer) {
signal = account.network.request(Api.functions.channels.getMessages(channel: inputChannel, id: [messageId.id]))
}
}
if let signal = signal {
return signal
|> `catch` { _ -> Signal<Api.messages.Messages, NoError> in
return .single(.messages(messages: [], chats: [], users: []))
}
|> mapToSignal { result -> Signal<Void, NoError> in
return account.postbox.modify { modifier -> Void in
let apiMessages: [Api.Message]
let apiChats: [Api.Chat]
let apiUsers: [Api.User]
switch result {
case let .messages(messages, chats, users):
apiMessages = messages
apiChats = chats
apiUsers = users
case let .messagesSlice(_, messages, chats, users):
apiMessages = messages
apiChats = chats
apiUsers = users
case let .channelMessages(_, _, _, messages, chats, users):
apiMessages = messages
apiChats = chats
apiUsers = users
}
var peers: [PeerId: Peer] = [:]
for user in apiUsers {
if let user = TelegramUser.merge(modifier.getPeer(user.peerId) as? TelegramUser, rhs: user) {
peers[user.id] = user
}
}
for chat in apiChats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers[groupOrChannel.id] = groupOrChannel
}
}
updatePeers(modifier: modifier, peers: Array(peers.values), update: { _, updated in
return updated
})
for message in apiMessages {
if let message = StoreMessage(apiMessage: message) {
let _ = modifier.addMessages([message], location: .Random)
}
}
}
}
} else {
return .complete()
}
} else {
return .complete()
}
}

View File

@ -245,12 +245,43 @@ public final class TelegramMediaFile: Media, Equatable {
}
public func isEqual(_ other: Media) -> Bool {
if let other = other as? TelegramMediaFile {
if self.fileId == other.fileId {
return true
}
guard let other = other as? TelegramMediaFile else {
return false
}
return false
if self.fileId != other.fileId {
return false
}
if !self.resource.isEqual(to: other.resource) {
return false
}
if self.previewRepresentations != other.previewRepresentations {
return false
}
if self.size != other.size {
return false
}
if self.mimeType != other.mimeType {
return false
}
/*if self.attributes != other.attributes {
return false
}*/
return true
}
public func withUpdatedSize(_ size: Int?) -> TelegramMediaFile {
return TelegramMediaFile(fileId: self.fileId, resource: self.resource, previewRepresentations: self.previewRepresentations, mimeType: self.mimeType, size: size, attributes: self.attributes)
}
public func withUpdatedPreviewRepresentations(_ previewRepresentations: [TelegramMediaImageRepresentation]) -> TelegramMediaFile {
return TelegramMediaFile(fileId: self.fileId, resource: self.resource, previewRepresentations: previewRepresentations, mimeType: self.mimeType, size: self.size, attributes: self.attributes)
}
}

View File

@ -0,0 +1,40 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public enum UpdateContactNameError {
case generic
}
public func updateContactName(account: Account, peerId: PeerId, firstName: String, lastName: String) -> Signal<Void, UpdateContactNameError> {
return account.postbox.modify { modifier -> Signal<Void, UpdateContactNameError> in
if let peer = modifier.getPeer(peerId) as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
return account.network.request(Api.functions.contacts.importContacts(contacts: [Api.InputContact.inputPhoneContact(clientId: 1, phone: phone, firstName: firstName, lastName: lastName)], replace: .boolFalse))
|> mapError { _ -> UpdateContactNameError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateContactNameError> in
return account.postbox.modify { modifier -> Void in
switch result {
case let .importedContacts(_, _, users):
if let first = users.first {
let user = TelegramUser(user: first)
updatePeers(modifier: modifier, peers: [user], update: { _, updated in
return updated
})
}
}
} |> mapError { _ -> UpdateContactNameError in return .generic }
}
} else {
return .fail(.generic)
}
} |> mapError { _ -> UpdateContactNameError in return .generic } |> switchToLatest
}

View File

@ -0,0 +1,25 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public func webpagePreview(account: Account, url: String) -> Signal<TelegramMediaWebpage?, NoError> {
return account.network.request(Api.functions.messages.getWebPagePreview(message: url))
|> `catch` { _ -> Signal<Api.MessageMedia, NoError> in
return .single(.messageMediaEmpty)
}
|> map { result -> TelegramMediaWebpage? in
switch result {
case let .messageMediaWebPage(webpage):
return telegramMediaWebpageFromApiWebpage(webpage)
default:
return nil
}
}
}