diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 3f5c6e6ae6..b13003e586 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -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 = ""; }; D050F2501E4A59C200988324 /* JoinLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLink.swift; sourceTree = ""; }; D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedLocalInputActivities.swift; sourceTree = ""; }; + D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleMessageView.swift; sourceTree = ""; }; + D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateContactName.swift; sourceTree = ""; }; + D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebpagePreview.swift; sourceTree = ""; }; D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerInfo.swift; sourceTree = ""; }; D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = ""; }; D0613FC91E60440600202CDB /* InvitationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitationLinks.swift; sourceTree = ""; }; @@ -963,6 +972,8 @@ D0AAD1A91E32638500D5B9DE /* ApplyMaxReadIndexInteractively.swift */, D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */, C239BE961E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift */, + D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */, + D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */, ); name = Messages; sourceTree = ""; @@ -1105,6 +1116,7 @@ D033FEB21E61F3C000644997 /* ReportPeer.swift */, D033FEB51E61F3F900644997 /* BlockedPeers.swift */, C239BE9B1E630CA700C2C453 /* UpdatePinnedMessage.swift */, + D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */, ); name = Peers; sourceTree = ""; @@ -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 */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 3f4acaf834..d5b768634d 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -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)? = nil) { +public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ resourceData: MediaResourceData, _ representation: CachedMediaResourceRepresentation) -> Signal +public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: Media, _ userInteractive: Bool) -> Signal + +public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) { account.postbox.mediaBox.fetchResource = { [weak account] resource, range -> Signal 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()) diff --git a/TelegramCore/AddressNames.swift b/TelegramCore/AddressNames.swift index 7ef6434a9f..050fd9a09c 100644 --- a/TelegramCore/AddressNames.swift +++ b/TelegramCore/AddressNames.swift @@ -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 diff --git a/TelegramCore/CloudFileMediaResource.swift b/TelegramCore/CloudFileMediaResource.swift index b66ef06eff..8e13a111c2 100644 --- a/TelegramCore/CloudFileMediaResource.swift +++ b/TelegramCore/CloudFileMediaResource.swift @@ -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 } diff --git a/TelegramCore/EnqueueMessage.swift b/TelegramCore/EnqueueMessage.swift index 52e84d666c..0a216cece3 100644 --- a/TelegramCore/EnqueueMessage.swift +++ b/TelegramCore/EnqueueMessage.swift @@ -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 { + 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 { diff --git a/TelegramCore/Fetch.swift b/TelegramCore/Fetch.swift index 593f290af1..02eda11e0c 100644 --- a/TelegramCore/Fetch.swift +++ b/TelegramCore/Fetch.swift @@ -16,7 +16,6 @@ private func fetchCloudMediaLocation(account: Account, resource: TelegramCloudMe #if os(iOS) private func fetchPhotoLibraryResource(localIdentifier: String) -> Signal { 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 Signal) -> Signal { 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)) } diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index 2484e46d14..d093195709 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -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 { diff --git a/TelegramCore/MultipartFetch.swift b/TelegramCore/MultipartFetch.swift index f9c41fe20d..040ce30c0c 100644 --- a/TelegramCore/MultipartFetch.swift +++ b/TelegramCore/MultipartFetch.swift @@ -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 diff --git a/TelegramCore/MultipartUpload.swift b/TelegramCore/MultipartUpload.swift index 024c61cb5a..1de1023976 100644 --- a/TelegramCore/MultipartUpload.swift +++ b/TelegramCore/MultipartUpload.swift @@ -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) diff --git a/TelegramCore/OutgoingMessageInfoAttribute.swift b/TelegramCore/OutgoingMessageInfoAttribute.swift index fe521a9c47..6738ff2ead 100644 --- a/TelegramCore/OutgoingMessageInfoAttribute.swift +++ b/TelegramCore/OutgoingMessageInfoAttribute.swift @@ -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) } } diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index 60b66ced2b..a351dcd399 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -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 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) diff --git a/TelegramCore/PendingMessageUploadedContent.swift b/TelegramCore/PendingMessageUploadedContent.swift index a7f56cfea1..49d3e86d2b 100644 --- a/TelegramCore/PendingMessageUploadedContent.swift +++ b/TelegramCore/PendingMessageUploadedContent.swift @@ -20,7 +20,7 @@ enum PendingMessageUploadedContentResult { case content(Message, PendingMessageUploadedContent) } -func uploadedMessageContent(network: Network, postbox: Postbox, message: Message) -> Signal { +func uploadedMessageContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, message: Message) -> Signal { 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 { - 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 { + return multipartUpload(network: network, postbox: postbox, resource: image.resource, encrypt: false) + |> mapToSignal { result -> Signal 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 { + 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 + if let transformOutgoingMessageMedia = transformOutgoingMessageMedia, !alreadyTransformed { + transform = .single(.pending) |> then(transformOutgoingMessageMedia(postbox, network, file, false) + |> mapToSignal { media -> Signal 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 = .single(.pending) |> then(transform + |> mapToSignal { media -> Signal 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 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() + } + } + } } diff --git a/TelegramCore/SecretChatOutgoingOperation.swift b/TelegramCore/SecretChatOutgoingOperation.swift index a8055ef633..6e368242a0 100644 --- a/TelegramCore/SecretChatOutgoingOperation.swift +++ b/TelegramCore/SecretChatOutgoingOperation.swift @@ -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") } diff --git a/TelegramCore/SetSecretChatMessageAutoremoveTimeoutInteractively.swift b/TelegramCore/SetSecretChatMessageAutoremoveTimeoutInteractively.swift index c5546d84a8..137994b5dc 100644 --- a/TelegramCore/SetSecretChatMessageAutoremoveTimeoutInteractively.swift +++ b/TelegramCore/SetSecretChatMessageAutoremoveTimeoutInteractively.swift @@ -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))]) } } } diff --git a/TelegramCore/SingleMessageView.swift b/TelegramCore/SingleMessageView.swift new file mode 100644 index 0000000000..af32738512 --- /dev/null +++ b/TelegramCore/SingleMessageView.swift @@ -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 { + return Signal { subscriber in + let loadedMessage = account.postbox.modify { modifier -> Signal 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 { + if let peer = modifier.getPeer(messageId.peerId) { + var signal: Signal? + 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 in + return .single(.messages(messages: [], chats: [], users: [])) + } + |> mapToSignal { result -> Signal 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() + } +} diff --git a/TelegramCore/TelegramMediaFile.swift b/TelegramCore/TelegramMediaFile.swift index b5d1ca39f1..887f51f6c4 100644 --- a/TelegramCore/TelegramMediaFile.swift +++ b/TelegramCore/TelegramMediaFile.swift @@ -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) } } diff --git a/TelegramCore/UpdateContactName.swift b/TelegramCore/UpdateContactName.swift new file mode 100644 index 0000000000..be728ed8db --- /dev/null +++ b/TelegramCore/UpdateContactName.swift @@ -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 { + return account.postbox.modify { modifier -> Signal 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 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 +} diff --git a/TelegramCore/WebpagePreview.swift b/TelegramCore/WebpagePreview.swift new file mode 100644 index 0000000000..f872d5e0a2 --- /dev/null +++ b/TelegramCore/WebpagePreview.swift @@ -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 { + return account.network.request(Api.functions.messages.getWebPagePreview(message: url)) + |> `catch` { _ -> Signal in + return .single(.messageMediaEmpty) + } + |> map { result -> TelegramMediaWebpage? in + switch result { + case let .messageMediaWebPage(webpage): + return telegramMediaWebpageFromApiWebpage(webpage) + default: + return nil + } + } +}