From 3306b36ddb76eb2d35269bd2f60b2410539ee6bc Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Thu, 1 Mar 2018 03:54:11 +0400 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 6 + TelegramCore/Account.swift | 1 + .../AccountStateManagementUtils.swift | 8 +- .../ApplyMaxReadIndexInteractively.swift | 74 ++-- TelegramCore/ApplyUpdateMessage.swift | 15 +- TelegramCore/CallSessionManager.swift | 10 +- TelegramCore/ContentPrivacySettings.swift | 66 ++++ TelegramCore/InlineBotMessageAttribute.swift | 30 +- .../ManagedSecretChatOutgoingOperations.swift | 324 +++++++++++++++--- TelegramCore/Namespaces.swift | 8 + ...OutgoingMessageWithChatContextResult.swift | 106 +----- .../PendingMessageUploadedContent.swift | 50 ++- ...ecretChatIncomingDecryptedOperations.swift | 160 ++++++++- TelegramCore/SecretChatLayerNegotiation.swift | 2 +- TelegramCore/StoreMessage_Telegram.swift | 4 +- TelegramCore/TelegramMediaWebpage.swift | 25 +- TelegramCore/UpdateSecretChat.swift | 14 +- TelegramCore/WebpagePreview.swift | 6 +- 18 files changed, 657 insertions(+), 252 deletions(-) create mode 100644 TelegramCore/ContentPrivacySettings.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index ace902b5e3..ded77b6296 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -593,6 +593,8 @@ D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; }; D0F8C3A02017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; }; D0F8C3A12017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; }; + D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; }; + D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; }; D0FA0ABD1E76C908005BB9B7 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; }; D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; }; D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; }; @@ -957,6 +959,7 @@ D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = ""; }; D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFeedPeers.swift; sourceTree = ""; }; D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = ""; }; + D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = ""; }; D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = ""; }; D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = ""; }; D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = ""; }; @@ -1028,6 +1031,7 @@ D01C7ED51EF5E468008305F1 /* ProxySettings.swift */, D0B167221F9F972E00976B40 /* LoggingSettings.swift */, D0AF32301FACEDEC0097362B /* CoreSettings.swift */, + D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */, ); name = Settings; sourceTree = ""; @@ -2079,6 +2083,7 @@ D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */, D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */, C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */, + D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */, D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */, D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */, D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */, @@ -2298,6 +2303,7 @@ C210DD631FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift in Sources */, D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */, D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */, + D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */, D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */, D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */, D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 741c094565..4c9807e79c 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -253,6 +253,7 @@ private var declaredEncodables: Void = { declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) }) declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) }) declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) }) + declareEncodable(ContentPrivacySettings.self, f: { ContentPrivacySettings(decoder: $0) }) return }() diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index 984f52d4d6..98c850214d 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -701,7 +701,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, case let .webPageEmpty(id): updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: - if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) { + if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { updatedState.updateMedia(webpage.webpageId, media: webpage) } } @@ -838,7 +838,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, case let .webPageEmpty(id): updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: - if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) { + if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { updatedState.updateMedia(webpage.webpageId, media: webpage) } } @@ -1539,7 +1539,7 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt case let .webPageEmpty(id): updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: - if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) { + if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { updatedState.updateMedia(webpage.webpageId, media: webpage) } } @@ -1975,7 +1975,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif peerIdsWithAddedSecretMessages.insert(peerId) } case let .ReadSecretOutbox(peerId, maxTimestamp, actionTimestamp): - applyOutgoingReadMaxIndex(modifier: modifier, index: MessageIndex.upperBound(peerId: peerId, timestamp: maxTimestamp, namespace: Namespaces.Message.Local), beginAt: actionTimestamp) + applyOutgoingReadMaxIndex(modifier: modifier, index: MessageIndex.upperBound(peerId: peerId, timestamp: maxTimestamp, namespace: Namespaces.Message.Local), beginCountdownAt: actionTimestamp) case let .AddPeerInputActivity(chatPeerId, peerId, activity): if let peerId = peerId { if updatedTypingActivities[chatPeerId] == nil { diff --git a/TelegramCore/ApplyMaxReadIndexInteractively.swift b/TelegramCore/ApplyMaxReadIndexInteractively.swift index f59b647ba1..6cd904a238 100644 --- a/TelegramCore/ApplyMaxReadIndexInteractively.swift +++ b/TelegramCore/ApplyMaxReadIndexInteractively.swift @@ -44,32 +44,66 @@ public func applyMaxReadIndexInteractively(postbox: Postbox, network: Network, s } } -func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginAt timestamp: Int32) { +func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginCountdownAt timestamp: Int32) { let messageIds = modifier.applyOutgoingReadMaxIndex(index) if index.id.peerId.namespace == Namespaces.Peer.SecretChat { for id in messageIds { - if let message = modifier.getMessage(id), !message.flags.contains(.Incoming) { - for attribute in message.attributes { - if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { - if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia { - 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, authorSignature: forwardInfo.authorSignature) + applySecretOutgoingMessageReadActions(modifier: modifier, id: id, beginCountdownAt: timestamp) + } + } +} + +func maybeReadSecretOutgoingMessage(modifier: Modifier, index: MessageIndex) { + guard index.id.peerId.namespace == Namespaces.Peer.SecretChat else { + assertionFailure() + return + } + guard index.id.namespace == Namespaces.Message.Local else { + assertionFailure() + return + } + + guard let combinedState = modifier.getCombinedPeerReadState(index.id.peerId) else { + return + } + + if combinedState.isOutgoingMessageIndexRead(index) { + applySecretOutgoingMessageReadActions(modifier: modifier, id: index.id, beginCountdownAt: index.timestamp) + } +} + +func applySecretOutgoingMessageReadActions(modifier: Modifier, id: MessageId, beginCountdownAt timestamp: Int32) { + guard id.peerId.namespace == Namespaces.Peer.SecretChat else { + assertionFailure() + return + } + guard id.namespace == Namespaces.Message.Local else { + assertionFailure() + return + } + + if let message = modifier.getMessage(id), !message.flags.contains(.Incoming) { + if message.flags.intersection([.Unsent, .Sending, .Failed]).isEmpty { + for attribute in message.attributes { + if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { + if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia { + 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, authorSignature: forwardInfo.authorSignature) + } + let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in + if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute { + return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp) + } else { + return currentAttribute } - let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in - if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute { - return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp) - } else { - return currentAttribute - } - }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) - modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) - } - break + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + }) + modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } + break } } } diff --git a/TelegramCore/ApplyUpdateMessage.swift b/TelegramCore/ApplyUpdateMessage.swift index caae48fcc4..7afcac2920 100644 --- a/TelegramCore/ApplyUpdateMessage.swift +++ b/TelegramCore/ApplyUpdateMessage.swift @@ -55,6 +55,10 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var sentStickers: [TelegramMediaFile] = [] var sentGifs: [TelegramMediaFile] = [] + if let updatedTimestamp = updatedTimestamp { + modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp) + } + modifier.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId if let messageId = messageId { @@ -140,9 +144,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) }) - if let updatedTimestamp = updatedTimestamp { - modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, timestamp: updatedTimestamp) - } for file in sentStickers { modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } @@ -198,6 +199,10 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage var updatedGroupingKey: Int64? + if let latestIndex = mapping.last?.1 { + modifier.offsetPendingMessagesTimestamps(lowerBound: latestIndex.id, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp) + } + for (message, _, updatedMessage) in mapping { modifier.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId @@ -259,10 +264,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage modifier.updateMessageGroupingKeysAtomically(mapping.map { $0.1.id }, groupingKey: updatedGroupingKey) } - if let latestIndex = mapping.last?.1 { - modifier.offsetPendingMessagesTimestamps(lowerBound: latestIndex.id, timestamp: latestIndex.timestamp) - } - for file in sentStickers { modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } diff --git a/TelegramCore/CallSessionManager.swift b/TelegramCore/CallSessionManager.swift index 475c75845e..fd79f45b28 100644 --- a/TelegramCore/CallSessionManager.swift +++ b/TelegramCore/CallSessionManager.swift @@ -179,6 +179,8 @@ private final class CallSessionContext { var state: CallSessionInternalState let subscribers = Bag<(CallSession) -> Void>() + let acknowledgeIncomingCallDisposable = MetaDisposable() + var isEmpty: Bool { if case .terminated = self.state { return self.subscribers.isEmpty @@ -192,6 +194,10 @@ private final class CallSessionContext { self.isOutgoing = isOutgoing self.state = state } + + deinit { + self.acknowledgeIncomingCallDisposable.dispose() + } } private final class CallSessionManagerContext { @@ -304,7 +310,9 @@ private final class CallSessionManagerContext { if randomStatus == 0 { let internalId = CallSessionInternalId() - self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b)) + let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b)) + self.contexts[internalId] = context + context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start()) self.contextIdByStableId[stableId] = internalId self.contextUpdated(internalId: internalId) self.ringingStatesUpdated() diff --git a/TelegramCore/ContentPrivacySettings.swift b/TelegramCore/ContentPrivacySettings.swift new file mode 100644 index 0000000000..84a3985ad9 --- /dev/null +++ b/TelegramCore/ContentPrivacySettings.swift @@ -0,0 +1,66 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public final class ContentPrivacySettings: PreferencesEntry, Equatable { + public let enableSecretChatWebpagePreviews: Bool? + + public static var defaultSettings = ContentPrivacySettings(enableSecretChatWebpagePreviews: nil) + + public init(enableSecretChatWebpagePreviews: Bool?) { + self.enableSecretChatWebpagePreviews = enableSecretChatWebpagePreviews + } + + public init(decoder: PostboxDecoder) { + self.enableSecretChatWebpagePreviews = decoder.decodeOptionalInt32ForKey("enableSecretChatWebpagePreviews").flatMap { $0 != 0 } + } + + public func encode(_ encoder: PostboxEncoder) { + if let enableSecretChatWebpagePreviews = self.enableSecretChatWebpagePreviews { + encoder.encodeInt32(enableSecretChatWebpagePreviews ? 1 : 0, forKey: "enableSecretChatWebpagePreviews") + } else { + encoder.encodeNil(forKey: "enableSecretChatWebpagePreviews") + } + } + + public func withUpdatedEnableSecretChatWebpagePreviews(_ enableSecretChatWebpagePreviews: Bool) -> ContentPrivacySettings { + return ContentPrivacySettings(enableSecretChatWebpagePreviews: enableSecretChatWebpagePreviews) + } + + public func isEqual(to: PreferencesEntry) -> Bool { + guard let to = to as? ContentPrivacySettings else { + return false + } + + return self == to + } + + public static func ==(lhs: ContentPrivacySettings, rhs: ContentPrivacySettings) -> Bool { + if lhs.enableSecretChatWebpagePreviews != rhs.enableSecretChatWebpagePreviews { + return false + } + return true + } +} + +public func updateContentPrivacySettings(postbox: Postbox, _ f: @escaping (ContentPrivacySettings) -> ContentPrivacySettings) -> Signal { + return postbox.modify { modifier -> Void in + var updated: ContentPrivacySettings? + modifier.updatePreferencesEntry(key: PreferencesKeys.contentPrivacySettings, { current in + if let current = current as? ContentPrivacySettings { + updated = f(current) + return updated + } else { + updated = f(ContentPrivacySettings.defaultSettings) + return updated + } + }) + } +} diff --git a/TelegramCore/InlineBotMessageAttribute.swift b/TelegramCore/InlineBotMessageAttribute.swift index a334ab64c7..c49f186678 100644 --- a/TelegramCore/InlineBotMessageAttribute.swift +++ b/TelegramCore/InlineBotMessageAttribute.swift @@ -6,21 +6,41 @@ import Foundation #endif public class InlineBotMessageAttribute: MessageAttribute { - public let peerId: PeerId + public let peerId: PeerId? + public let title: String? public var associatedPeerIds: [PeerId] { - return [self.peerId] + if let peerId = self.peerId { + return [peerId] + } else { + return [] + } } - init(peerId: PeerId) { + init(peerId: PeerId?, title: String?) { self.peerId = peerId + self.title = title } required public init(decoder: PostboxDecoder) { - self.peerId = PeerId(decoder.decodeInt64ForKey("i", orElse: 0)) + if let peerId = decoder.decodeOptionalInt64ForKey("i") { + self.peerId = PeerId(peerId) + } else { + self.peerId = nil + } + self.title = decoder.decodeOptionalStringForKey("t") } public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt64(self.peerId.toInt64(), forKey: "i") + if let peerId = self.peerId { + encoder.encodeInt64(peerId.toInt64(), forKey: "i") + } else { + encoder.encodeNil(forKey: "i") + } + if let title = self.title { + encoder.encodeString(title, forKey: "t") + } else { + encoder.encodeNil(forKey: "t") + } } } diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index 8618b805ec..e3b4f46951 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -517,7 +517,46 @@ private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute]) - return result } -private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, layer: SecretChatLayer) -> BoxedDecryptedMessage { +private func decryptedEntities73(_ entities: [MessageTextEntity]?) -> [SecretApi73.MessageEntity]? { + guard let entities = entities else { + return nil + } + + var result: [SecretApi73.MessageEntity] = [] + for entity in entities { + switch entity.type { + case .Unknown: + break + case .Mention: + result.append(.messageEntityMention(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Hashtag: + result.append(.messageEntityHashtag(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .BotCommand: + result.append(.messageEntityBotCommand(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Url: + result.append(.messageEntityUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Email: + result.append(.messageEntityEmail(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Bold: + result.append(.messageEntityBold(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Italic: + result.append(.messageEntityItalic(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Code: + result.append(.messageEntityCode(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count))) + case .Pre: + result.append(.messageEntityPre(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), language: "")) + case let .TextUrl(url): + result.append(.messageEntityTextUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), url: url)) + case .TextMention: + break + case .PhoneNumber: + break + } + } + return result +} + +private func boxedDecryptedMessage(modifier: Modifier, message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, thumbnailData: [MediaId: Data], layer: SecretChatLayer) -> BoxedDecryptedMessage { let media: Media? = message.media.first var messageAutoremoveTimeout: Int32 = 0 var replyGlobalId: Int64? = nil @@ -532,37 +571,83 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up } } + var viaBotName: String? + var entities: [MessageTextEntity]? + for attribute in message.attributes { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { messageAutoremoveTimeout = attribute.timeout - break + } else if let attribute = attribute as? InlineBotMessageAttribute { + if let title = attribute.title { + viaBotName = title + } else if let peerId = attribute.peerId, let peer = modifier.getPeer(peerId), let addressName = peer.addressName { + viaBotName = addressName + } + } else if let attribute = attribute as? TextEntitiesMessageAttribute { + entities = attribute.entities } } if let media = media { if let image = media as? TelegramMediaImage, let uploadedFile = uploadedFile, let largestRepresentation = largestImageRepresentation(image.representations) { + let thumbW: Int32 + let thumbH: Int32 + let thumb: Buffer + if let smallestRepresentation = smallestImageRepresentation(image.representations), let data = thumbnailData[image.imageId] { + thumbW = Int32(smallestRepresentation.dimensions.width) + thumbH = Int32(smallestRepresentation.dimensions.height) + thumb = Buffer(data: data) + } else { + thumbW = 90 + thumbH = 90 + thumb = Buffer() + } + switch layer { case .layer8: let randomBytesData = malloc(15)! arc4random_buf(randomBytesData, 15) let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true) - let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv)) + let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv)) return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia)) case .layer46: - let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "") + if let _ = viaBotName { + flags |= (1 << 11) + } + let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "") flags |= (1 << 9) - return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId)) + return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId)) case .layer73: - let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "") + if let _ = viaBotName { + flags |= (1 << 11) + } + let decryptedEntites = entities.flatMap(decryptedEntities73) + if let _ = decryptedEntites { + flags |= (1 << 7) + } + let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "") flags |= (1 << 9) if message.groupingKey != nil { flags |= (1 << 17) } - return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) + return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) } } else if let file = media as? TelegramMediaFile { + let thumbW: Int32 + let thumbH: Int32 + let thumb: Buffer + if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let data = thumbnailData[file.fileId] { + thumbW = Int32(smallestRepresentation.dimensions.width) + thumbH = Int32(smallestRepresentation.dimensions.height) + thumb = Buffer(data: data) + } else { + thumbW = 0 + thumbH = 0 + thumb = Buffer() + } + switch layer { case .layer8: if let uploadedFile = uploadedFile { @@ -570,7 +655,7 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up arc4random_buf(randomBytesData, 15) let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true) - let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv)) + let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv)) return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia)) } @@ -591,17 +676,26 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up if let voiceDuration = voiceDuration { decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv)) } else { - decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes), caption: "") + decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes), caption: "") } } else { if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size { - decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: SecretApi46.PhotoSize.photoSizeEmpty(type: "s"), dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes)) + let thumb: SecretApi46.PhotoSize + if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource { + thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0) + } else { + thumb = SecretApi46.PhotoSize.photoSizeEmpty(type: "s") + } + decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes)) } } if let decryptedMedia = decryptedMedia { + if let _ = viaBotName { + flags |= (1 << 11) + } flags |= (1 << 9) - return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId)) + return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId)) } case .layer73: var decryptedMedia: SecretApi73.DecryptedMessageMedia? @@ -620,19 +714,62 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up if let voiceDuration = voiceDuration { decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv)) } else { - decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes73(file.attributes), caption: "") + decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes73(file.attributes), caption: "") } } else { if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size { - decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: SecretApi73.PhotoSize.photoSizeEmpty(type: "s"), dcId: Int32(resource.datacenterId), attributes: decryptedAttributes73(file.attributes)) + let thumb: SecretApi73.PhotoSize + if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource { + thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0) + } else { + thumb = SecretApi73.PhotoSize.photoSizeEmpty(type: "s") + } + decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes73(file.attributes)) } } if let decryptedMedia = decryptedMedia { + if let _ = viaBotName { + flags |= (1 << 11) + } + let decryptedEntites = entities.flatMap(decryptedEntities73) + if let _ = decryptedEntites { + flags |= (1 << 7) + } flags |= (1 << 9) - return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) + return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) } } + } else if let webpage = media as? TelegramMediaWebpage { + var url: String? + if case let .Loaded(content) = webpage.content { + url = content.url + } + + if let url = url, !url.isEmpty { + switch layer { + case .layer8: + break + case .layer46: + if let _ = viaBotName { + flags |= (1 << 11) + } + let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url) + flags |= (1 << 9) + return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId)) + case .layer73: + if let _ = viaBotName { + flags |= (1 << 11) + } + let decryptedEntites = entities.flatMap(decryptedEntities73) + if let _ = decryptedEntites { + flags |= (1 << 7) + } + let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url) + flags |= (1 << 9) + return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) + } + } } } @@ -644,9 +781,19 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty)) case .layer46: - return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId)) + if let _ = viaBotName { + flags |= (1 << 11) + } + return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId)) case .layer73: - return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) + if let _ = viaBotName { + flags |= (1 << 11) + } + let decryptedEntites = entities.flatMap(decryptedEntities73) + if let _ = decryptedEntites { + flags |= (1 << 7) + } + return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey)) } } @@ -828,21 +975,66 @@ private func replaceOutgoingOperationWithEmptyMessage(modifier: Modifier, peerId } } +private func resourceThumbnailData(mediaBox: MediaBox, resource: MediaResource, mediaId: MediaId) -> Signal<(MediaId, Data)?, NoError> { + return mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + |> take(1) + |> map { data -> (MediaId, Data)? in + if data.complete, data.size < 1024 * 16, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + return (mediaId, content) + } else { + return nil + } + } +} + +private func messageWithThumbnailData(mediaBox: MediaBox, message: Message) -> Signal<[MediaId: Data], NoError> { + var signals: [Signal<(MediaId, Data)?, NoError>] = [] + for media in message.media { + if let image = media as? TelegramMediaImage { + if let smallestRepresentation = smallestImageRepresentation(image.representations) { + signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: image.imageId)) + } + } else if let file = media as? TelegramMediaFile { + if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) { + signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: file.fileId)) + } + } + } + return combineLatest(signals) + |> map { values in + var result: [MediaId: Data] = [:] + for value in values { + if let value = value { + result[value.0] = value.1 + } + } + return result + } +} + private func sendMessage(postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal { - return postbox.modify { modifier -> Signal in - if let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState, let peer = modifier.getPeer(messageId.peerId) as? TelegramSecretChat { - if let message = modifier.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId { - let decryptedMessage = boxedDecryptedMessage(message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, layer: layer) - return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered) - |> mapToSignal { result in - return postbox.modify { modifier -> Void in - if result == nil { - replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: globallyUniqueId) - } else { - markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil) - } - modifier.updateMessage(message.id, update: { currentMessage in - var flags = StoreMessageFlags(currentMessage.flags) + return postbox.modify { modifier -> Signal<[MediaId: Data], NoError> in + if let message = modifier.getMessage(messageId) { + return messageWithThumbnailData(mediaBox: postbox.mediaBox, message: message) + } else { + return .single([:]) + } + } + |> switchToLatest + |> mapToSignal { thumbnailData -> Signal in + return postbox.modify { modifier -> Signal in + if let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState, let peer = modifier.getPeer(messageId.peerId) as? TelegramSecretChat { + if let message = modifier.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId { + let decryptedMessage = boxedDecryptedMessage(modifier: modifier, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, thumbnailData: thumbnailData, layer: layer) + return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered) + |> mapToSignal { result in + return postbox.modify { modifier -> Void in + if result == nil { + replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: globallyUniqueId) + } else { + markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil) + } + var timestamp = message.timestamp if let result = result { switch result { @@ -851,30 +1043,40 @@ private func sendMessage(postbox: Postbox, network: Network, messageId: MessageI 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, authorSignature: forwardInfo.authorSignature) } - modifier.offsetPendingMessagesTimestamps(lowerBound: currentMessage.id, timestamp: timestamp) + modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([messageId]), timestamp: timestamp) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) - }) - } + modifier.updateMessage(message.id, update: { currentMessage in + var flags = StoreMessageFlags(currentMessage.flags) + if let _ = result { + 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, authorSignature: forwardInfo.authorSignature) + } + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + }) + + maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: message.id, timestamp: timestamp)) + } + } + } else { + replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: arc4random64()) + modifier.deleteMessages([messageId]) + //assertionFailure() + return .complete() } } else { - assertionFailure() - return .never() + return .complete() } - } else { - return .complete() - } - } |> switchToLatest + } |> switchToLatest + } } private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal { @@ -890,6 +1092,7 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil) } if let messageId = action.messageId { + var resultTimestamp: Int32? modifier.updateMessage(messageId, update: { currentMessage in var flags = StoreMessageFlags(currentMessage.flags) var timestamp = currentMessage.timestamp @@ -905,12 +1108,17 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId } else { flags = [.Failed] } + resultTimestamp = timestamp var storeForwardInfo: StoreMessageForwardInfo? if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) } return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) + + if let resultTimestamp = resultTimestamp { + maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: messageId, timestamp: resultTimestamp)) + } } } } @@ -924,12 +1132,21 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: let payload = Buffer() var sequenceInfo: SecretChatOperationSequenceInfo? var maybeParameters: SecretChatEncryptionParameters? + + let mode: SecretChatEncryptionMode + switch decryptedMessage { + case .layer8, .layer46: + mode = .v1 + default: + mode = .v2(role: state.role) + } + switch state.embeddedState { case .terminated, .handshake: break case .basicLayer: if let key = state.keychain.indefinitelyValidKey() { - maybeParameters = SecretChatEncryptionParameters(key: key, mode: .v1) + maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode) } case let .sequenceBasedLayer(sequenceState): let topReceivedOperationIndex: Int32 @@ -940,14 +1157,7 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: } let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex) if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) { - let mode: SecretChatEncryptionMode - switch sequenceState.layerNegotiationState.activeLayer { - case .layer73: - mode = .v2(role: state.role) - default: - mode = .v1 - } - maybeParameters = SecretChatEncryptionParameters(key: key, mode: .v1) + maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode) } Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeParameters?.key.fingerprint))") sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex) diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index a955f6c1a6..0b84b4352e 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -25,6 +25,7 @@ public struct Namespaces { public static let CloudSecretFile: Int32 = 10 public static let CloudGame: Int32 = 11 public static let CloudInvoice: Int32 = 12 + public static let LocalWebpage: Int32 = 13 } public struct Peer { @@ -132,6 +133,7 @@ private enum PreferencesKeyValues: Int32 { case proxySettings = 5 case loggingSettings = 6 case coreSettings = 7 + case contentPrivacySettings = 8 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -188,4 +190,10 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue) return key }() + + public static let contentPrivacySettings: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.contentPrivacySettings.rawValue) + return key + }() } diff --git a/TelegramCore/OutgoingMessageWithChatContextResult.swift b/TelegramCore/OutgoingMessageWithChatContextResult.swift index c8444ef20c..f861f6281a 100644 --- a/TelegramCore/OutgoingMessageWithChatContextResult.swift +++ b/TelegramCore/OutgoingMessageWithChatContextResult.swift @@ -12,114 +12,10 @@ private func aspectFitSize(_ size: CGSize, to: CGSize) -> CGSize { return CGSize(width: floor(size.width * scale), height: floor(size.height * scale)) } -/* -if ([result isKindOfClass:[TGBotContextMediaResult class]]) { - TGBotContextMediaResult *concreteResult = (TGBotContextMediaResult *)result; - if ([concreteResult.type isEqualToString:@"game"]) { - TGGameMediaAttachment *gameMedia = [[TGGameMediaAttachment alloc] initWithGameId:0 accessHash:0 shortName:nil title:concreteResult.title gameDescription:concreteResult.resultDescription photo:concreteResult.photo document:concreteResult.document]; - [strongSelf->_companion controllerWantsToSendGame:gameMedia asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; - } else if (concreteResult.document != nil) { - TGDocumentAttributeVideo *video = nil; - bool isAnimated = false; - for (id attribute in concreteResult.document.attributes) { - if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) { - video = attribute; - } else if ([attribute isKindOfClass:[TGDocumentAttributeAnimated class]]) { - isAnimated = true; - } - } - - if (video != nil && !isAnimated) { - TGVideoMediaAttachment *videoMedia = [[TGVideoMediaAttachment alloc] init]; - videoMedia = [[TGVideoMediaAttachment alloc] init]; - videoMedia.videoId = concreteResult.document.documentId; - videoMedia.accessHash = concreteResult.document.accessHash; - videoMedia.duration = video.duration; - videoMedia.dimensions = video.size; - videoMedia.thumbnailInfo = concreteResult.document.thumbnailInfo; - TGVideoInfo *videoInfo = [[TGVideoInfo alloc] init]; - [videoInfo addVideoWithQuality:1 url:[[NSString alloc] initWithFormat:@"video:%lld:%lld:%d:%d", videoMedia.videoId, videoMedia.accessHash, concreteResult.document.datacenterId, concreteResult.document.size] size:concreteResult.document.size]; - videoMedia.videoInfo = videoInfo; - [strongSelf->_companion controllerWantsToSendRemoteVideoWithMedia:videoMedia asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - } else { - [strongSelf->_companion controllerWantsToSendRemoteDocument:concreteResult.document asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - } - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; - } else if (concreteResult.photo != nil) { - [strongSelf->_companion controllerWantsToSendRemoteImage:concreteResult.photo text:concreteMessage.caption asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; - } -} else if ([result isKindOfClass:[TGBotContextExternalResult class]]) { - TGBotContextExternalResult *concreteResult = (TGBotContextExternalResult *)result; - if ([concreteResult.type isEqualToString:@"gif"]) { - TGExternalGifSearchResult *externalGifSearchResult = [[TGExternalGifSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl size:concreteResult.size]; - id description = [strongSelf->_companion documentDescriptionFromExternalGifSearchResult:externalGifSearchResult text:concreteMessage.caption botContextResult:botContextResult]; - if (description != nil) { - [strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; - [TGRecentContextBotsSignal addRecentBot:results.userId]; - } - } else if ([concreteResult.type isEqualToString:@"photo"]) { - TGExternalImageSearchResult *externalImageSearchResult = [[TGExternalImageSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl title:concreteResult.title size:concreteResult.size]; - id description = [strongSelf->_companion imageDescriptionFromExternalImageSearchResult:externalImageSearchResult text:concreteMessage.caption botContextResult:botContextResult]; - if (description != nil) { - [strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; - [TGRecentContextBotsSignal addRecentBot:results.userId]; - } - } else if ([concreteResult.type isEqualToString:@"audio"] || [concreteResult.type isEqualToString:@"voice"] || [concreteResult.type isEqualToString:@"file"]) { - id description = [strongSelf->_companion documentDescriptionFromBotContextResult:concreteResult text:concreteMessage.caption botContextResult:botContextResult]; - if (description != nil) { - [strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; - [TGRecentContextBotsSignal addRecentBot:results.userId]; - } - } else { - if (![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) { - for (id result in [TGMessage textCheckingResultsForText:concreteMessage.caption highlightMentionsAndTags:false highlightCommands:false entities:nil]) { - if ([result isKindOfClass:[NSTextCheckingResult class]] && ((NSTextCheckingResult *)result).resultType == NSTextCheckingTypeLink) { - [_companion maybeAskForSecretWebpages]; - return; - } - } - } - - [strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.caption entities:@[] asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - } -} -} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageText class]]) { - TGBotContextResultSendMessageText *concreteMessage = (TGBotContextResultSendMessageText *)result.sendMessage; - - if (![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) { - for (id result in [TGMessage textCheckingResultsForText:concreteMessage.message highlightMentionsAndTags:false highlightCommands:false entities:nil]) { - if ([result isKindOfClass:[NSTextCheckingResult class]] && ((NSTextCheckingResult *)result).resultType == NSTextCheckingTypeLink) { - [_companion maybeAskForSecretWebpages]; - return; - } - } - } - - [strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.message entities:concreteMessage.entities asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; -} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageGeo class]]) { - TGBotContextResultSendMessageGeo *concreteMessage = (TGBotContextResultSendMessageGeo *)result.sendMessage; - [strongSelf->_companion controllerWantsToSendMapWithLatitude:concreteMessage.location.latitude longitude:concreteMessage.location.longitude venue:concreteMessage.location.venue asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; -} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageContact class]]) { - TGBotContextResultSendMessageContact *concreteMessage = (TGBotContextResultSendMessageContact *)result.sendMessage; - TGUser *contactUser = [[TGUser alloc] init]; - contactUser.firstName = concreteMessage.contact.firstName; - contactUser.lastName = concreteMessage.contact.lastName; - contactUser.phoneNumber = concreteMessage.contact.phoneNumber; - [strongSelf->_companion controllerWantsToSendContact:contactUser asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup]; - [strongSelf->_inputTextPanel.inputField setText:@"" animated:true]; -} -}*/ - public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) -> EnqueueMessage? { var attributes: [MessageAttribute] = [] attributes.append(OutgoingChatContextResultMessageAttribute(queryId: results.queryId, id: result.id)) - attributes.append(InlineBotMessageAttribute(peerId: results.botId)) + attributes.append(InlineBotMessageAttribute(peerId: results.botId, title: nil)) switch result.message { case let .auto(caption, entities, replyMarkup): diff --git a/TelegramCore/PendingMessageUploadedContent.swift b/TelegramCore/PendingMessageUploadedContent.swift index 5fe66e32d2..3171528216 100644 --- a/TelegramCore/PendingMessageUploadedContent.swift +++ b/TelegramCore/PendingMessageUploadedContent.swift @@ -40,7 +40,9 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods var autoremoveAttribute: AutoremoveTimeoutMessageAttribute? for attribute in attributes { if let attribute = attribute as? OutgoingChatContextResultMessageAttribute { - contextResult = attribute + if peerId.namespace != Namespaces.Peer.SecretChat { + contextResult = attribute + } } else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { autoremoveAttribute = attribute } @@ -332,9 +334,14 @@ private enum UploadedMediaTransform { case done(Media?) } +private enum UploadedMediaThumbnailResult { + case file(Api.InputFile) + case none +} + private enum UploadedMediaThumbnail { case pending - case done(Api.InputFile?) + case done(UploadedMediaThumbnailResult) } private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal { @@ -441,27 +448,40 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili 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) - |> mapError { _ -> PendingMessageUploadError in return .generic } - |> map { result in - return .done(result) - } + if peerId.namespace == Namespaces.Peer.SecretChat { + return .single(.done(.none)) + } else { + return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail) + |> mapError { _ -> PendingMessageUploadError in return .generic } + |> map { result in + if let result = result { + return .done(.file(result)) + } else { + return .done(.none) + } + } + } } else { - return .single(.done(nil)) + return .single(.done(.none)) } } }) return combineLatest(upload, thumbnail) - |> mapToSignal { content, media -> Signal in + |> mapToSignal { content, thumbnailResult -> Signal in switch content { case let .progress(progress): return .single(.progress(progress)) case let .inputFile(inputFile): - if case let .done(thumbnail) = media { + if case let .done(thumbnail) = thumbnailResult { var flags: Int32 = 0 - if let _ = thumbnail { + var thumbnailFile: Api.InputFile? + if case let .file(file) = thumbnail { + thumbnailFile = file + } + + if let thumbnailFile = thumbnailFile { flags |= 1 << 2 } @@ -473,10 +493,8 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } } - //flags |= 1 << 3 - if ttlSeconds != nil { - return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text))) + return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text))) } return postbox.modify { modifier -> Api.InputPeer? in @@ -485,7 +503,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili |> mapError { _ -> PendingMessageUploadError in return .generic } |> mapToSignal { inputPeer -> Signal in if let inputPeer = inputPeer { - return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds))) + return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds))) |> mapError { _ -> PendingMessageUploadError in return .generic } |> mapToSignal { result -> Signal in switch result { @@ -506,7 +524,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili return .complete() } case let .inputSecretFile(file, size, key): - if case .done = media { + if case .done = thumbnailResult { return .single(.content(.secretMedia(file, size, key))) } else { return .complete() diff --git a/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift b/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift index f758131336..3bf1c8b1f0 100644 --- a/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift @@ -488,8 +488,6 @@ extension TelegramMediaFileAttribute { self = .Sticker(displayText: alt, packReference: packReference, maskData: nil) case let .documentAttributeVideo(duration, w, h): self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []) - default: - return nil } } } @@ -531,9 +529,42 @@ extension TelegramMediaFileAttribute { } } +private func parseEntities(_ entities: [SecretApi46.MessageEntity]?) -> TextEntitiesMessageAttribute { + var result: [MessageTextEntity] = [] + if let entities = entities { + for entity in entities { + switch entity { + case let .messageEntityMention(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention)) + case let .messageEntityHashtag(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag)) + case let .messageEntityBotCommand(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand)) + case let .messageEntityUrl(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url)) + case let .messageEntityEmail(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email)) + case let .messageEntityBold(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold)) + case let .messageEntityItalic(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic)) + case let .messageEntityCode(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code)) + case let .messageEntityPre(offset, length, _): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre)) + case let .messageEntityTextUrl(offset, length, url): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url))) + case .messageEntityUnknown: + break + } + } + } + return TextEntitiesMessageAttribute(entities: result) +} + private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi46.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? { switch apiMessage { - case let .decryptedMessage(flags, randomId, ttl, message, media, entities, viaBotName, replyToRandomId): + case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId): var text = message var parsedMedia: [Media] = [] var attributes: [MessageAttribute] = [] @@ -543,6 +574,12 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil)) } + attributes.append(parseEntities(entities)) + + if let viaBotName = viaBotName, !viaBotName.isEmpty { + attributes.append(InlineBotMessageAttribute(peerId: nil, title: viaBotName)) + } + if let media = media { switch media { case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption): @@ -590,7 +627,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - var parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: arc4random64()) @@ -600,16 +637,47 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) } - case let .decryptedMessageMediaExternalDocument(id, accessHash, date, mimeType, size, thumb, dcId, attributes): + case let .decryptedMessageMediaExternalDocument(id, accessHash, _, mimeType, size, thumb, dcId, attributes): var parsedAttributes: [TelegramMediaFileAttribute] = [] for attribute in attributes { if let parsedAttribute = TelegramMediaFileAttribute(attribute) { parsedAttributes.append(parsedAttribute) } } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + var previewRepresentations: [TelegramMediaImageRepresentation] = [] + switch thumb { + case let .photoSize(_, location, w, h, size): + switch location { + case let .fileLocation(dcId, volumeId, localId, secret): + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size)))) + case .fileLocationUnavailable: + break + } + case let .photoCachedSize(_, location, w, h, bytes): + if bytes.size > 0 { + switch location { + case let .fileLocation(dcId, volumeId, localId, secret): + let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size) + resources.append((resource, bytes.makeData())) + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) + case .fileLocationUnavailable: + break + } + } + default: + break + } + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) - default: + case let .decryptedMessageMediaWebPage(url): + parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url))) + case let .decryptedMessageMediaGeoPoint(lat, long): + parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)) + case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId): + parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))) + case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId): + parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil)) + case .decryptedMessageMediaEmpty: break } } @@ -658,9 +726,42 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } } +private func parseEntities(_ entities: [SecretApi73.MessageEntity]?) -> TextEntitiesMessageAttribute { + var result: [MessageTextEntity] = [] + if let entities = entities { + for entity in entities { + switch entity { + case let .messageEntityMention(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention)) + case let .messageEntityHashtag(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag)) + case let .messageEntityBotCommand(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand)) + case let .messageEntityUrl(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url)) + case let .messageEntityEmail(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email)) + case let .messageEntityBold(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold)) + case let .messageEntityItalic(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic)) + case let .messageEntityCode(offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code)) + case let .messageEntityPre(offset, length, _): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre)) + case let .messageEntityTextUrl(offset, length, url): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url))) + case .messageEntityUnknown: + break + } + } + } + return TextEntitiesMessageAttribute(entities: result) +} + private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi73.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? { switch apiMessage { - case let .decryptedMessage(flags, randomId, ttl, message, media, entities, viaBotName, replyToRandomId, groupedId): + case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId, groupedId): var text = message var parsedMedia: [Media] = [] var attributes: [MessageAttribute] = [] @@ -670,6 +771,12 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil)) } + attributes.append(parseEntities(entities)) + + if let viaBotName = viaBotName, !viaBotName.isEmpty { + attributes.append(InlineBotMessageAttribute(peerId: nil, title: viaBotName)) + } + if let media = media { switch media { case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption): @@ -717,7 +824,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - var parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: arc4random64()) @@ -734,9 +841,40 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 parsedAttributes.append(parsedAttribute) } } + var previewRepresentations: [TelegramMediaImageRepresentation] = [] + switch thumb { + case let .photoSize(_, location, w, h, size): + switch location { + case let .fileLocation(dcId, volumeId, localId, secret): + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size)))) + case .fileLocationUnavailable: + break + } + case let .photoCachedSize(_, location, w, h, bytes): + if bytes.size > 0 { + switch location { + case let .fileLocation(dcId, volumeId, localId, secret): + let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size) + resources.append((resource, bytes.makeData())) + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) + case .fileLocationUnavailable: + break + } + } + default: + break + } let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) - default: + case let .decryptedMessageMediaWebPage(url): + parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url))) + case let .decryptedMessageMediaGeoPoint(lat, long): + parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)) + case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId): + parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))) + case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId): + parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil)) + case .decryptedMessageMediaEmpty: break } } @@ -771,7 +909,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { - case let .decryptedMessageActionDeleteMessages(randomIds): + case .decryptedMessageActionDeleteMessages: return nil case .decryptedMessageActionFlushHistory: return nil diff --git a/TelegramCore/SecretChatLayerNegotiation.swift b/TelegramCore/SecretChatLayerNegotiation.swift index 9b693514a3..f27add1412 100644 --- a/TelegramCore/SecretChatLayerNegotiation.swift +++ b/TelegramCore/SecretChatLayerNegotiation.swift @@ -24,7 +24,7 @@ func secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modi switch state.embeddedState { case .basicLayer: var updatedState = state - updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: topSupportedLayer.secretChatLayer, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState) + updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer8, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState) return updatedState case let .sequenceBasedLayer(sequenceState): var updatedState = state diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index 78d4c51261..0207573d9a 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -299,7 +299,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI return (TelegramMediaExpiredContent(data: .file), nil) } case let .messageMediaWebPage(webpage): - if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) { + if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) { return (mediaWebpage, nil) } case .messageMediaUnsupported: @@ -475,7 +475,7 @@ extension StoreMessage { } if let viaBotId = viaBotId { - attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId))) + attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil)) } if let replyToMsgId = replyToMsgId { diff --git a/TelegramCore/TelegramMediaWebpage.swift b/TelegramCore/TelegramMediaWebpage.swift index ff67ea5ea4..b1f4b51cae 100644 --- a/TelegramCore/TelegramMediaWebpage.swift +++ b/TelegramCore/TelegramMediaWebpage.swift @@ -191,7 +191,7 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage } public enum TelegramMediaWebpageContent { - case Pending(Int32) + case Pending(Int32, String?) case Loaded(TelegramMediaWebpageLoadedContent) } @@ -213,7 +213,7 @@ public final class TelegramMediaWebpage: Media, Equatable { self.webpageId = MediaId(decoder.decodeBytesForKeyNoCopy("i")!) if decoder.decodeInt32ForKey("ct", orElse: 0) == 0 { - self.content = .Pending(decoder.decodeInt32ForKey("pendingDate", orElse: 0)) + self.content = .Pending(decoder.decodeInt32ForKey("pendingDate", orElse: 0), decoder.decodeOptionalStringForKey("pendingUrl")) } else { self.content = .Loaded(TelegramMediaWebpageLoadedContent(decoder: decoder)) } @@ -225,9 +225,14 @@ public final class TelegramMediaWebpage: Media, Equatable { encoder.encodeBytes(buffer, forKey: "i") switch self.content { - case let .Pending(date): + case let .Pending(date, url): encoder.encodeInt32(0, forKey: "ct") encoder.encodeInt32(date, forKey: "pendingDate") + if let url = url { + encoder.encodeString(url, forKey: "pendingUrl") + } else { + encoder.encodeNil(forKey: "pendingUrl") + } case let .Loaded(loadedContent): encoder.encodeInt32(1, forKey: "ct") loadedContent.encode(encoder) @@ -251,10 +256,14 @@ public final class TelegramMediaWebpage: Media, Equatable { } switch lhs.content { - case let .Pending(lhsDate): + case let .Pending(lhsDate, lhsUrl): switch rhs.content { - case let .Pending(rhsDate) where lhsDate == rhsDate: - return true + case let .Pending(rhsDate, rhsUrl): + if lhsDate == rhsDate, lhsUrl == rhsUrl { + return true + } else { + return false + } default: return false } @@ -269,12 +278,12 @@ public final class TelegramMediaWebpage: Media, Equatable { } } -func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? { +func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? { switch webpage { case .webPageNotModified: return nil case let .webPagePending(id, date): - return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date)) + return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url)) case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage): var embedSize: CGSize? if let embedWidth = embedWidth, let embedHeight = embedHeight { diff --git a/TelegramCore/UpdateSecretChat.swift b/TelegramCore/UpdateSecretChat.swift index 05b52fb56a..7b221c5e07 100644 --- a/TelegramCore/UpdateSecretChat.swift +++ b/TelegramCore/UpdateSecretChat.swift @@ -44,19 +44,9 @@ func updateSecretChat(accountPeerId: PeerId, modifier: Modifier, chat: Api.Encry } var updatedState = currentState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer).withUpdatedKeyFingerprint(SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key)))) + + updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: currentPeer.id, state: updatedState) - var layer: SecretChatLayer? - switch updatedState.embeddedState { - case .terminated, .handshake: - break - case .basicLayer: - layer = .layer8 - case let .sequenceBasedLayer(sequenceState): - layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer - } - if let layer = layer { - updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: currentPeer.id, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState) - } modifier.setPeerChatState(currentPeer.id, state: updatedState) updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in return updated diff --git a/TelegramCore/WebpagePreview.swift b/TelegramCore/WebpagePreview.swift index c4cd5e5353..ae5d306e6e 100644 --- a/TelegramCore/WebpagePreview.swift +++ b/TelegramCore/WebpagePreview.swift @@ -21,7 +21,7 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? = |> mapToSignal { result -> Signal in switch result { case let .messageMediaWebPage(webpage): - if let media = telegramMediaWebpageFromApiWebpage(webpage) { + if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) { if case .Loaded = media.content { return .single(media) } else { @@ -45,9 +45,9 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg return .single(.webPageNotModified) } |> mapToSignal { result -> Signal in - if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { + if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { return postbox.modify { modifier -> TelegramMediaWebpage in - modifier.updateMedia(updatedWebpage.webpageId, update: updatedWebpage) + modifier.updateMedia(webpage.webpageId, update: updatedWebpage) return updatedWebpage } } else {