From 93fe592eb954cdca8b2f84269cd2846ba5b00956 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 13 Jun 2017 21:45:17 +0300 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 26 ++ TelegramCore/Account.swift | 1 + TelegramCore/AccountIntermediateState.swift | 4 +- .../AccountStateManagementUtils.swift | 93 ++++-- TelegramCore/AccountViewTracker.swift | 26 +- TelegramCore/Api.swift | 63 ++-- TelegramCore/BotPaymentForm.swift | 168 ++++++++++ TelegramCore/ChannelAdmins.swift | 2 +- TelegramCore/ChannelBlacklist.swift | 2 +- TelegramCore/ChannelMembers.swift | 2 +- .../ChannelMessageStateVersionAttribute.swift | 22 ++ TelegramCore/ChannelParticipants.swift | 4 +- TelegramCore/ChannelState.swift | 20 +- .../HistoryViewChannelStateValidation.swift | 286 ++++++++++++++++++ TelegramCore/Holes.swift | 26 +- TelegramCore/InstantPage.swift | 46 ++- ...agedSynchronizePinnedChatsOperations.swift | 12 +- TelegramCore/Namespaces.swift | 1 - ...OutgoingMessageWithChatContextResult.swift | 4 +- TelegramCore/StoreMessage_Telegram.swift | 11 +- TelegramCore/SynchronizePeerReadState.swift | 29 ++ TelegramCore/TelegramMediaFile.swift | 26 +- TelegramCore/TelegramMediaInvoice.swift | 41 ++- TelegramCore/TelegramMediaWebDocument.swift | 12 +- 24 files changed, 828 insertions(+), 99 deletions(-) create mode 100644 TelegramCore/BotPaymentForm.swift create mode 100644 TelegramCore/ChannelMessageStateVersionAttribute.swift create mode 100644 TelegramCore/HistoryViewChannelStateValidation.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 2e4135d28a..9bca0b7635 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -294,6 +294,8 @@ D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; }; D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */; }; D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; }; + D0754D2A1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */; }; + D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */; }; D07827BB1E00451F00071108 /* SearchPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827BA1E00451F00071108 /* SearchPeers.swift */; }; D07827C91E02F59C00071108 /* InstantPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827C81E02F59C00071108 /* InstantPage.swift */; }; D07827CB1E02F5B200071108 /* RichText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827CA1E02F5B200071108 /* RichText.swift */; }; @@ -313,6 +315,10 @@ D08F4A671E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */; }; D08F4A691E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; }; D08F4A6A1E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; }; + D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; }; + D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; }; + D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; }; + D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; }; D099EA1C1DE72867001AF5A8 /* PeerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */; }; D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */; }; D09A2FEB1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */; }; @@ -700,6 +706,7 @@ D067066E1D512AEB00DED3E3 /* MtProtoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MtProtoKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/MtProtoKit.framework"; sourceTree = ""; }; D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = ""; }; D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = ""; }; + D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotPaymentForm.swift; sourceTree = ""; }; D07827BA1E00451F00071108 /* SearchPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPeers.swift; sourceTree = ""; }; D07827C81E02F59C00071108 /* InstantPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPage.swift; sourceTree = ""; }; D07827CA1E02F5B200071108 /* RichText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RichText.swift; sourceTree = ""; }; @@ -712,6 +719,8 @@ D08CAA8B1ED81EDF0000FDA8 /* Localizations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localizations.swift; sourceTree = ""; }; D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeInstalledStickerPacksOperations.swift; sourceTree = ""; }; D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeInstalledStickerPacksOperations.swift; sourceTree = ""; }; + D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = ""; }; + D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewChannelStateValidation.swift; sourceTree = ""; }; D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = ""; }; D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = ""; }; D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAccessRestrictionInfo.swift; sourceTree = ""; }; @@ -1041,6 +1050,7 @@ D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */, D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */, D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */, + D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */, ); name = Attributes; sourceTree = ""; @@ -1163,6 +1173,7 @@ D0BEAF5C1E54941B00BD963D /* Authorization.swift */, D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */, D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */, + D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */, ); name = Account; sourceTree = ""; @@ -1252,6 +1263,14 @@ name = Frameworks; sourceTree = ""; }; + D0754D281EEE10D800884F6E /* Bot Payments */ = { + isa = PBXGroup; + children = ( + D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */, + ); + name = "Bot Payments"; + sourceTree = ""; + }; D08CAA821ED816290000FDA8 /* Localization */ = { isa = PBXGroup; children = ( @@ -1306,6 +1325,7 @@ D03B0D691D631A9200955575 /* Contacts */, D03B0D6E1D631AA900955575 /* Messages */, D0DF0C881D819C5F008AEB01 /* Peers */, + D0754D281EEE10D800884F6E /* Bot Payments */, D0C50E2F1E93A83B00F62E39 /* Calls */, D021E0E01DB5400200C6B04F /* Sticker Management */, D05A32DF1E6F096B002760B4 /* Settings */, @@ -1656,6 +1676,7 @@ D00DBBDA1E64E67E00DB5485 /* UpdateSecretChat.swift in Sources */, D0E23DD51E8042F500B9B6D2 /* FeaturedStickerPack.swift in Sources */, D0FA8B981E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */, + D0754D2A1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */, D0DF0C911D81A857008AEB01 /* ImageRepresentationsUtils.swift in Sources */, D08CAA8C1ED81EDF0000FDA8 /* Localizations.swift in Sources */, D0E6521F1E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */, @@ -1681,6 +1702,7 @@ D049EAD51E43D98500A2CD3A /* RecentMediaItem.swift in Sources */, D0C50E341E93A86600F62E39 /* CallSessionManager.swift in Sources */, D00D34421E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, + D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */, C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */, D03B0D0A1D62255C00955575 /* Holes.swift in Sources */, D0B843CB1DA7FF30005F29E1 /* NBPhoneNumberUtil.m in Sources */, @@ -1769,6 +1791,7 @@ D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D0FA8B9E1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */, D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, + D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */, D0613FCA1E60440600202CDB /* InvitationLinks.swift in Sources */, D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */, D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */, @@ -1884,6 +1907,7 @@ D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */, D0B477741EBF54A20033A0AB /* RecentCalls.swift in Sources */, D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */, + D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */, D0B844311DAB91E0005F29E1 /* NBPhoneMetaData.m in Sources */, C22EE61C1E67418000334C38 /* ToggleChannelSignatures.swift in Sources */, D0B418AC1D7E0597004562A4 /* Network.swift in Sources */, @@ -1950,6 +1974,7 @@ D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */, D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */, + D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */, D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */, D0B4189B1D7E0580004562A4 /* TelegramMediaWebpage.swift in Sources */, D00C7CE11E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */, @@ -2050,6 +2075,7 @@ D0F7B1E41E045C7B007EB8A5 /* InstantPage.swift in Sources */, D03E5E0D1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */, D0B418AD1D7E0597004562A4 /* Serialization.swift in Sources */, + D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */, D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */, D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */, D0FA8BBA1E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 6c779535fb..96216cdf7e 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -230,6 +230,7 @@ private var declaredEncodables: Void = { declareEncodable(LocalizationSettings.self, f: { LocalizationSettings(decoder: $0) }) declareEncodable(SuggestedLocalizationEntry.self, f: { SuggestedLocalizationEntry(decoder: $0) }) declareEncodable(SynchronizeLocalizationUpdatesOperation.self, f: { SynchronizeLocalizationUpdatesOperation(decoder: $0) }) + declareEncodable(ChannelMessageStateVersionAttribute.self, f: { ChannelMessageStateVersionAttribute(decoder: $0) }) return }() diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index 3a0e332d70..a00a4ff10d 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -17,8 +17,9 @@ final class AccountInitialState { let peerNotificationSettings: [PeerId: PeerNotificationSettings] let peerIdsWithNewMessages: Set let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]] + let cloudReadStates: [PeerId: PeerReadState] - init(state: AuthorizedAccountState.State, peerIds: Set, messageIds: Set, peerIdsWithNewMessages: Set, channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) { + init(state: AuthorizedAccountState.State, peerIds: Set, messageIds: Set, peerIdsWithNewMessages: Set, channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState]) { self.state = state self.peerIds = peerIds self.messageIds = messageIds @@ -26,6 +27,7 @@ final class AccountInitialState { self.peerIdsWithNewMessages = peerIdsWithNewMessages self.peerNotificationSettings = peerNotificationSettings self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps + self.cloudReadStates = cloudReadStates } } diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index bc326829f2..a20d4acac9 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -284,6 +284,7 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set, var peerNotificationSettings: [PeerId: PeerNotificationSettings] = [:] var readInboxMaxIds: [PeerId: MessageId] = [:] + var cloudReadStates: [PeerId: PeerReadState] = [:] for peerId in peerIdsWithNewMessages { if let notificationSettings = modifier.getPeerNotificationSettings(peerId) { @@ -292,6 +293,7 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set, if let readStates = modifier.getPeerReadStates(peerId) { for (namespace, state) in readStates { if namespace == Namespaces.Message.Cloud { + cloudReadStates[peerId] = state switch state { case let .idBased(maxIncomingReadId, _, _, _): readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: maxIncomingReadId) @@ -304,7 +306,7 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set, } } - return AccountMutableState(initialState: AccountInitialState(state: (modifier.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, messageIds: associatedMessageIds, peerIdsWithNewMessages: peerIdsWithNewMessages, channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps), initialPeers: peers, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp) + return AccountMutableState(initialState: AccountInitialState(state: (modifier.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, messageIds: associatedMessageIds, peerIdsWithNewMessages: peerIdsWithNewMessages, channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates), initialPeers: peers, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp) } func initialStateWithUpdateGroups(_ account: Account, groups: [UpdateGroup]) -> Signal { @@ -620,7 +622,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, //Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update") } else if previousState.pts + ptsCount == pts { updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })) - updatedState.updateChannelState(peerId, state: previousState.setPts(pts)) + updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts)) } else { if !channelsToPoll.contains(peerId) { Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) delete pts hole") @@ -646,8 +648,10 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.addPreCachedResource(resource, data: data) } } - updatedState.editMessage(messageId, message: message) - updatedState.updateChannelState(peerId, state: previousState.setPts(pts)) + var attributes = message.attributes + attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) + updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes)) + updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts)) } else { if !channelsToPoll.contains(peerId) { Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) edit message pts hole") @@ -678,7 +682,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, } } - updatedState.updateChannelState(peerId, state: previousState.setPts(pts)) + updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts)) } else { if !channelsToPoll.contains(peerId) { Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateWebPage pts hole") @@ -712,8 +716,10 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.addPreCachedResource(resource, data: data) } } - updatedState.addMessages([message], location: .UpperHistoryBlock) - updatedState.updateChannelState(message.id.peerId, state: previousState.setPts(pts)) + var attributes = message.attributes + attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) + updatedState.addMessages([message.withUpdatedAttributes(attributes)], location: .UpperHistoryBlock) + updatedState.updateChannelState(message.id.peerId, state: previousState.withUpdatedPts(pts)) } else { if !channelsToPoll.contains(message.id.peerId) { Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) message pts hole") @@ -919,7 +925,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, case let .updateUserStatus(userId, status): updatedState.mergePeerPresences([PeerId(namespace: Namespaces.Peer.CloudUser, id: userId): TelegramUserPresence(apiStatus: status)]) case let .updateUserName(userId, firstName, lastName, username): - //TODO add contact checing for apply first and last name + //TODO add contact checking for apply first and last name updatedState.updatePeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), { peer in if let user = peer as? TelegramUser { return user.withUpdatedUsername(username) @@ -1021,8 +1027,11 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, return resolveAssociatedMessages(account: account, state: finalState) |> mapToSignal { resultingState -> Signal in return resolveMissingPeerNotificationSettings(account: account, state: resultingState) - |> map { resultingState -> AccountFinalState in - return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError, incomplete: missingUpdates) + |> mapToSignal { resultingState -> Signal in + return resolveMissingPeerCloudReadStates(account: account, state: resultingState) + |> map { resultingState -> AccountFinalState in + return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError, incomplete: missingUpdates) + } } } } @@ -1136,6 +1145,47 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Acc } } +private func resolveMissingPeerCloudReadStates(account: Account, state: AccountMutableState) -> Signal { + var missingPeers: [PeerId: Api.InputPeer] = [:] + + for peerId in state.initialState.peerIdsWithNewMessages { + if state.initialState.cloudReadStates[peerId] == nil && (peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup) { + if let peer = state.peers[peerId], let inputPeer = apiInputPeer(peer) { + missingPeers[peerId] = inputPeer + } else { + Logger.shared.log("State", "can't fetch notification settings for peer \(peerId): can't create inputPeer") + } + } + } + + if missingPeers.isEmpty { + return .single(state) + } else { + Logger.shared.log("State", "will fetch cloud read states for \(missingPeers.count) peers") + + var signals: [Signal<(PeerId, PeerReadState)?, NoError>] = [] + for (peerId, inputPeer) in missingPeers { + let fetchSettings = fetchPeerCloudReadState(network: account.network, postbox: account.postbox, peerId: peerId, inputPeer: inputPeer) + |> map { state -> (PeerId, PeerReadState)? in + return state.flatMap { (peerId, $0) } + } + signals.append(fetchSettings) + } + return combineLatest(signals) + |> map { peersAndSettings -> AccountMutableState in + var updatedState = state + for pair in peersAndSettings { + if let (peerId, state) = pair { + if case let .idBased(maxIncomingReadId, maxOutgoingReadId, maxKnownId, count) = state { + updatedState.resetReadState(peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxIncomingReadId, maxOutgoingReadId: maxOutgoingReadId, maxKnownId: maxKnownId, count: count) + } + } + } + return updatedState + } + } +} + func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountStateManager) -> Signal { return account.postbox.modify { modifier -> Signal in if let accountState = (modifier.getState() as? AuthorizedAccountState)?.state, let peer = modifier.getPeer(peerId) { @@ -1148,7 +1198,7 @@ func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountS if let notificationSettings = modifier.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { peerNotificationSettings[peerId] = notificationSettings } - let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), messageIds: Set(), peerIdsWithNewMessages: Set(), channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: [:]), initialPeers: initialPeers, initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) + let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), messageIds: Set(), peerIdsWithNewMessages: Set(), channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:]), initialPeers: initialPeers, initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) return pollChannel(account, peer: peer, state: initialState) |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(account: account, state: finalState) @@ -1173,7 +1223,11 @@ func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountS private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> { if let inputChannel = apiInputChannel(peer) { - return (account.network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: 20)) + var limit: Int32 = 20 + #if (arch(i386) || arch(x86_64)) && os(iOS) + limit = 3 + #endif + return (account.network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: limit)) |> map { Optional($0) } |> `catch` { error -> Signal in if error.errorDescription == "CHANNEL_PRIVATE" { @@ -1192,9 +1246,9 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt apiTimeout = timeout let channelState: ChannelState if let previousState = updatedState.channelStates[peer.id] { - channelState = previousState.setPts(pts) + channelState = previousState.withUpdatedPts(pts) } else { - channelState = ChannelState(pts: pts) + channelState = ChannelState(pts: pts, invalidatedPts: nil) } updatedState.updateChannelState(peer.id, state: channelState) @@ -1225,20 +1279,15 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt let channelState: ChannelState if let previousState = updatedState.channelStates[peer.id] { - channelState = previousState.setPts(pts) + channelState = previousState.withUpdatedPts(pts) } else { - channelState = ChannelState(pts: pts) + channelState = ChannelState(pts: pts, invalidatedPts: nil) } updatedState.updateChannelState(peer.id, state: channelState) case let .channelDifferenceTooLong(_, pts, timeout, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, messages, chats, users): apiTimeout = timeout - let channelState: ChannelState - if let previousState = updatedState.channelStates[peer.id] { - channelState = previousState.setPts(pts) - } else { - channelState = ChannelState(pts: pts) - } + let channelState = ChannelState(pts: pts, invalidatedPts: pts) updatedState.updateChannelState(peer.id, state: channelState) updatedState.mergeChats(chats) diff --git a/TelegramCore/AccountViewTracker.swift b/TelegramCore/AccountViewTracker.swift index 173ae326d2..37bffe8615 100644 --- a/TelegramCore/AccountViewTracker.swift +++ b/TelegramCore/AccountViewTracker.swift @@ -133,6 +133,16 @@ private func fetchWebpage(account: Account, messageId: MessageId) -> Signal [AdditionalMessageHistoryViewData] { + var result = additionalData + if peerId.namespace == Namespaces.Peer.CloudChannel { + if result.index(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { + result.append(.peerChatState(peerId)) + } + } + return result +} + private final class PeerCachedDataContext { var viewIds = Set() var timestamp: Double? @@ -196,8 +206,12 @@ public final class AccountViewTracker { private var channelPollingContexts: [PeerId: ChannelPollingContext] = [:] private var featuredStickerPacksContext: FeaturedStickerPacksContext? + private let historyViewChannelStateValidationContexts: HistoryViewChannelStateValidationContexts + init(account: Account) { self.account = account + + self.historyViewChannelStateValidationContexts = HistoryViewChannelStateValidationContexts(queue: self.queue, postbox: account.postbox, network: account.network) } deinit { @@ -482,10 +496,16 @@ public final class AccountViewTracker { if let strongSelf = self { let messageIds = pendingWebpages(entries: next.0.entries) strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds) + if peerId.namespace == Namespaces.Peer.CloudChannel { + strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: next.0) + } } }, disposed: { [weak self] viewId in if let strongSelf = self { strongSelf.updatePendingWebpages(viewId: viewId, messageIds: []) + if peerId.namespace == Namespaces.Peer.CloudChannel { + strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: nil) + } } }) @@ -511,7 +531,7 @@ public final class AccountViewTracker { public func aroundUnreadMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + let signal = account.postbox.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData)) return wrappedMessageHistorySignal(peerId: peerId, signal: signal) } else { return .never() @@ -520,7 +540,7 @@ public final class AccountViewTracker { public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData)) return wrappedMessageHistorySignal(peerId: peerId, signal: signal) } else { return .never() @@ -529,7 +549,7 @@ public final class AccountViewTracker { public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageIndex, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData)) return wrappedMessageHistorySignal(peerId: peerId, signal: signal) } else { return .never() diff --git a/TelegramCore/Api.swift b/TelegramCore/Api.swift index 6c6a1eed47..971f91cb11 100644 --- a/TelegramCore/Api.swift +++ b/TelegramCore/Api.swift @@ -52,6 +52,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[145955919] = { return Api.PageBlock.parse_pageBlockCollage($0) } dict[319588707] = { return Api.PageBlock.parse_pageBlockSlideshow($0) } dict[-283684427] = { return Api.PageBlock.parse_pageBlockChannel($0) } + dict[834148991] = { return Api.PageBlock.parse_pageBlockAudio($0) } dict[-614138572] = { return Api.account.TmpPassword.parse_tmpPassword($0) } dict[590459437] = { return Api.Photo.parse_photoEmpty($0) } dict[-1836524247] = { return Api.Photo.parse_photo($0) } @@ -352,8 +353,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1182234929] = { return Api.InputUser.parse_inputUserEmpty($0) } dict[-138301121] = { return Api.InputUser.parse_inputUserSelf($0) } dict[-668391402] = { return Api.InputUser.parse_inputUser($0) } - dict[-1913754556] = { return Api.Page.parse_pagePart($0) } - dict[-677274263] = { return Api.Page.parse_pageFull($0) } + dict[-1908433218] = { return Api.Page.parse_pagePart($0) } + dict[1433323434] = { return Api.Page.parse_pageFull($0) } dict[157948117] = { return Api.upload.File.parse_file($0) } dict[352864346] = { return Api.upload.File.parse_fileCdnRedirect($0) } dict[182649427] = { return Api.MessageRange.parse_messageRange($0) } @@ -2872,6 +2873,7 @@ public struct Api { case pageBlockCollage(items: [Api.PageBlock], caption: Api.RichText) case pageBlockSlideshow(items: [Api.PageBlock], caption: Api.RichText) case pageBlockChannel(channel: Api.Chat) + case pageBlockAudio(audioId: Int64, caption: Api.RichText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) -> Swift.Bool { switch self { @@ -3045,6 +3047,13 @@ public struct Api { } channel.serialize(buffer, true) break + case .pageBlockAudio(let audioId, let caption): + if boxed { + buffer.appendInt32(834148991) + } + serializeInt64(audioId, buffer: buffer, boxed: false) + caption.serialize(buffer, true) + break } return true } @@ -3391,6 +3400,22 @@ public struct Api { return nil } } + fileprivate static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockAudio(audioId: _1!, caption: _2!) + } + else { + return nil + } + } public var description: String { get { @@ -3439,6 +3464,8 @@ public struct Api { return "(pageBlockSlideshow items: \(items), caption: \(caption))" case .pageBlockChannel(let channel): return "(pageBlockChannel channel: \(channel))" + case .pageBlockAudio(let audioId, let caption): + return "(pageBlockAudio audioId: \(audioId), caption: \(caption))" } } } @@ -10795,14 +10822,14 @@ public struct Api { } public enum Page: CustomStringConvertible { - case pagePart(blocks: [Api.PageBlock], photos: [Api.Photo], videos: [Api.Document]) - case pageFull(blocks: [Api.PageBlock], photos: [Api.Photo], videos: [Api.Document]) + case pagePart(blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document]) + case pageFull(blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) -> Swift.Bool { switch self { - case .pagePart(let blocks, let photos, let videos): + case .pagePart(let blocks, let photos, let documents): if boxed { - buffer.appendInt32(-1913754556) + buffer.appendInt32(-1908433218) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(blocks.count)) @@ -10815,14 +10842,14 @@ public struct Api { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videos.count)) - for item in videos { + buffer.appendInt32(Int32(documents.count)) + for item in documents { item.serialize(buffer, true) } break - case .pageFull(let blocks, let photos, let videos): + case .pageFull(let blocks, let photos, let documents): if boxed { - buffer.appendInt32(-677274263) + buffer.appendInt32(1433323434) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(blocks.count)) @@ -10835,8 +10862,8 @@ public struct Api { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videos.count)) - for item in videos { + buffer.appendInt32(Int32(documents.count)) + for item in documents { item.serialize(buffer, true) } break @@ -10861,7 +10888,7 @@ public struct Api { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.Page.pagePart(blocks: _1!, photos: _2!, videos: _3!) + return Api.Page.pagePart(blocks: _1!, photos: _2!, documents: _3!) } else { return nil @@ -10884,7 +10911,7 @@ public struct Api { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.Page.pageFull(blocks: _1!, photos: _2!, videos: _3!) + return Api.Page.pageFull(blocks: _1!, photos: _2!, documents: _3!) } else { return nil @@ -10894,10 +10921,10 @@ public struct Api { public var description: String { get { switch self { - case .pagePart(let blocks, let photos, let videos): - return "(pagePart blocks: \(blocks), photos: \(photos), videos: \(videos))" - case .pageFull(let blocks, let photos, let videos): - return "(pageFull blocks: \(blocks), photos: \(photos), videos: \(videos))" + case .pagePart(let blocks, let photos, let documents): + return "(pagePart blocks: \(blocks), photos: \(photos), documents: \(documents))" + case .pageFull(let blocks, let photos, let documents): + return "(pageFull blocks: \(blocks), photos: \(photos), documents: \(documents))" } } } diff --git a/TelegramCore/BotPaymentForm.swift b/TelegramCore/BotPaymentForm.swift new file mode 100644 index 0000000000..281a82540f --- /dev/null +++ b/TelegramCore/BotPaymentForm.swift @@ -0,0 +1,168 @@ +import Foundation +#if os(macOS) + import PostboxMac + import MtProtoKitMac + import SwiftSignalKitMac +#else + import Postbox + import MtProtoKitDynamic + import SwiftSignalKit +#endif + +public struct BotPaymentInvoiceFields: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let name = BotPaymentInvoiceFields(rawValue: 1 << 0) + public static let phone = BotPaymentInvoiceFields(rawValue: 1 << 1) + public static let email = BotPaymentInvoiceFields(rawValue: 1 << 2) + public static let shippingAddress = BotPaymentInvoiceFields(rawValue: 1 << 3) + public static let flexibleShipping = BotPaymentInvoiceFields(rawValue: 1 << 4) +} + +public struct BotPaymentPrice { + public let label: String + public let amount: Int64 + + public init(label: String, amount: Int64) { + self.label = label + self.amount = amount + } +} + +public struct BotPaymentInvoice { + public let isTest: Bool + public let requestedFields: BotPaymentInvoiceFields + public let currency: String + public let prices: [BotPaymentPrice] +} + +public struct BotPaymentNativeProvider { + public let name: String + public let params: String +} + +public struct BotPaymentShippingAddress { + public let streetLine1: String + public let streetLine2: String + public let city: String + public let state: String + public let countryIso2: String + public let postCode: String + + public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) { + self.streetLine1 = streetLine1 + self.streetLine2 = streetLine2 + self.city = city + self.state = state + self.countryIso2 = countryIso2 + self.postCode = postCode + } +} + +public struct BotPaymentRequestedInfo { + public let name: String? + public let phone: String? + public let email: String? + public let shippingAddress: BotPaymentShippingAddress? + + public init(name: String?, phone: String?, email: String?, shippingAddress: BotPaymentShippingAddress?) { + self.name = name + self.phone = phone + self.email = email + self.shippingAddress = shippingAddress + } +} + +public enum BotPaymentSavedCredentials { + case card(id: String, title: String) +} + +public struct BotPaymentForm { + public let canSaveCredentials: Bool + public let passwordMissing: Bool + public let invoice: BotPaymentInvoice + public let providerId: Int32 + public let url: String + public let nativeProvider: BotPaymentNativeProvider? + public let savedInfo: BotPaymentRequestedInfo? + public let savedCredentials: BotPaymentSavedCredentials? +} + +public enum BotPaymentFormReuestError { + case generic +} + +public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: MessageId) -> Signal { + return network.request(Api.functions.payments.getPaymentForm(msgId: messageId.id)) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> map { result -> BotPaymentForm in + switch result { + case let .paymentForm(flags, _, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, _): + let parsedInvoice: BotPaymentInvoice + switch invoice { + case let .invoice(flags, currency, prices): + var fields = BotPaymentInvoiceFields() + if (flags & (1 << 1)) != 0 { + fields.insert(.name) + } + if (flags & (1 << 2)) != 0 { + fields.insert(.phone) + } + if (flags & (1 << 3)) != 0 { + fields.insert(.email) + } + if (flags & (1 << 4)) != 0 { + fields.insert(.shippingAddress) + } + if (flags & (1 << 5)) != 0 { + fields.insert(.flexibleShipping) + } + parsedInvoice = BotPaymentInvoice(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map { + switch $0 { + case let .labeledPrice(label, amount): + return BotPaymentPrice(label: label, amount: amount) + } + }) + } + var parsedNativeProvider: BotPaymentNativeProvider? + if let nativeProvider = nativeProvider, let nativeParams = nativeParams { + switch nativeParams { + case let .dataJSON(data): + parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data) + } + } + var parsedSavedInfo: BotPaymentRequestedInfo? + if let savedInfo = savedInfo { + switch savedInfo { + case let .paymentRequestedInfo(_, name, phone, email, shippingAddress): + var parsedShippingAddress: BotPaymentShippingAddress? + if let shippingAddress = shippingAddress { + switch shippingAddress { + case let .postAddress(streetLine1, streetLine2, city, state, countryIso2, postCode): + parsedShippingAddress = BotPaymentShippingAddress(streetLine1: streetLine1, streetLine2: streetLine2, city: city, state: state, countryIso2: countryIso2, postCode: postCode) + } + } + parsedSavedInfo = BotPaymentRequestedInfo(name: name, phone: phone, email: email, shippingAddress: parsedShippingAddress) + } + } + var parsedSavedCredentials: BotPaymentSavedCredentials? + if let savedCredentials = savedCredentials { + switch savedCredentials { + case let .paymentSavedCredentialsCard(id, title): + parsedSavedCredentials = .card(id: id, title: title) + } + } + return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: providerId, url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials) + } + } +} diff --git a/TelegramCore/ChannelAdmins.swift b/TelegramCore/ChannelAdmins.swift index a6b5a6f53f..446defe97f 100644 --- a/TelegramCore/ChannelAdmins.swift +++ b/TelegramCore/ChannelAdmins.swift @@ -27,7 +27,7 @@ public func channelAdmins(account: Account, peerId: PeerId) -> Signal<[RenderedC for participant in CachedChannelParticipants(apiParticipants: participants).participants { if let peer = peers[participant.peerId] { - items.append(RenderedChannelParticipant(participant: participant, peer: peer)) + items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers)) } } diff --git a/TelegramCore/ChannelBlacklist.swift b/TelegramCore/ChannelBlacklist.swift index 7dc4d64c93..17d333f1c8 100644 --- a/TelegramCore/ChannelBlacklist.swift +++ b/TelegramCore/ChannelBlacklist.swift @@ -38,7 +38,7 @@ private func fetchChannelBlacklist(account: Account, peerId: PeerId, filter: Cha for participant in CachedChannelParticipants(apiParticipants: participants).participants { if let peer = peers[participant.peerId] { - items.append(RenderedChannelParticipant(participant: participant, peer: peer)) + items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers)) } } } diff --git a/TelegramCore/ChannelMembers.swift b/TelegramCore/ChannelMembers.swift index 00c4866345..0ee4658602 100644 --- a/TelegramCore/ChannelMembers.swift +++ b/TelegramCore/ChannelMembers.swift @@ -38,7 +38,7 @@ public func channelMembers(account: Account, peerId: PeerId, filter: ChannelMemb for participant in CachedChannelParticipants(apiParticipants: participants).participants { if let peer = peers[participant.peerId] { - items.append(RenderedChannelParticipant(participant: participant, peer: peer)) + items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers)) } } } diff --git a/TelegramCore/ChannelMessageStateVersionAttribute.swift b/TelegramCore/ChannelMessageStateVersionAttribute.swift new file mode 100644 index 0000000000..b65e88e084 --- /dev/null +++ b/TelegramCore/ChannelMessageStateVersionAttribute.swift @@ -0,0 +1,22 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public class ChannelMessageStateVersionAttribute: MessageAttribute { + public let pts: Int32 + + public init(pts: Int32) { + self.pts = pts + } + + required public init(decoder: Decoder) { + self.pts = decoder.decodeInt32ForKey("p", orElse: 0) + } + + public func encode(_ encoder: Encoder) { + encoder.encodeInt32(self.pts, forKey: "p") + } +} diff --git a/TelegramCore/ChannelParticipants.swift b/TelegramCore/ChannelParticipants.swift index b7c69481e1..9ed1e43df0 100644 --- a/TelegramCore/ChannelParticipants.swift +++ b/TelegramCore/ChannelParticipants.swift @@ -12,10 +12,12 @@ import Foundation public struct RenderedChannelParticipant: Equatable { public let participant: ChannelParticipant public let peer: Peer + public let peers: [PeerId: Peer] - public init(participant: ChannelParticipant, peer: Peer) { + public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer]) { self.participant = participant self.peer = peer + self.peers = peers } public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool { diff --git a/TelegramCore/ChannelState.swift b/TelegramCore/ChannelState.swift index c7406080db..ef44a621ac 100644 --- a/TelegramCore/ChannelState.swift +++ b/TelegramCore/ChannelState.swift @@ -7,21 +7,33 @@ import Foundation final class ChannelState: PeerChatState, Equatable, CustomStringConvertible { let pts: Int32 + let invalidatedPts: Int32? - init(pts: Int32) { + init(pts: Int32, invalidatedPts: Int32?) { self.pts = pts + self.invalidatedPts = invalidatedPts } init(decoder: Decoder) { self.pts = decoder.decodeInt32ForKey("pts", orElse: 0) + self.invalidatedPts = decoder.decodeOptionalInt32ForKey("ipts") } func encode(_ encoder: Encoder) { encoder.encodeInt32(self.pts, forKey: "pts") + if let invalidatedPts = self.invalidatedPts { + encoder.encodeInt32(invalidatedPts, forKey: "ipts") + } else { + encoder.encodeNil(forKey: "ipts") + } } - func setPts(_ pts: Int32) -> ChannelState { - return ChannelState(pts: pts) + func withUpdatedPts(_ pts: Int32) -> ChannelState { + return ChannelState(pts: pts, invalidatedPts: self.invalidatedPts) + } + + func withUpdatedInvalidatedPts(_ invalidatedPts: Int32?) -> ChannelState { + return ChannelState(pts: self.pts, invalidatedPts: invalidatedPts) } func equals(_ other: PeerChatState) -> Bool { @@ -37,7 +49,7 @@ final class ChannelState: PeerChatState, Equatable, CustomStringConvertible { } func ==(lhs: ChannelState, rhs: ChannelState) -> Bool { - return lhs.pts == rhs.pts + return lhs.pts == rhs.pts && lhs.invalidatedPts == rhs.invalidatedPts } struct ChannelUpdate { diff --git a/TelegramCore/HistoryViewChannelStateValidation.swift b/TelegramCore/HistoryViewChannelStateValidation.swift new file mode 100644 index 0000000000..4b38d66aef --- /dev/null +++ b/TelegramCore/HistoryViewChannelStateValidation.swift @@ -0,0 +1,286 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ChannelStateValidationBatch { + private let disposable: Disposable + let invalidatedPts: Int32 + + var cancelledMessageIds = Set() + + init(disposable: Disposable, invalidatedPts: Int32) { + self.disposable = disposable + self.invalidatedPts = invalidatedPts + } + + deinit { + disposable.dispose() + } +} + +private final class ChannelStateValidationContext { + var batchReferences: [MessageId: ChannelStateValidationBatch] = [:] +} + +final class HistoryViewChannelStateValidationContexts { + private let queue: Queue + private let postbox: Postbox + private let network: Network + + private var contexts: [Int32: ChannelStateValidationContext] = [:] + + init(queue: Queue, postbox: Postbox, network: Network) { + self.queue = queue + self.postbox = postbox + self.network = network + } + + func updateView(id: Int32, view: MessageHistoryView?) { + if let view = view { + var channelState: ChannelState? + for entry in view.additionalData { + if case let .peerChatState(_, chatState) = entry { + if let chatState = chatState as? ChannelState { + channelState = chatState + } + break + } + } + + if let invalidatedPts = channelState?.invalidatedPts { + var invalidatedMessageIds: [MessageId] = [] + var minValidatedPts: Int32? + + for entry in view.entries { + switch entry { + case let .MessageEntry(message, _, _, _): + if message.id.namespace == Namespaces.Message.Cloud { + var messagePts: Int32? + inner: for attribute in message.attributes { + if let attribute = attribute as? ChannelMessageStateVersionAttribute { + messagePts = attribute.pts + break inner + } + } + if let messagePts = messagePts { + if messagePts < invalidatedPts { + if minValidatedPts == nil || minValidatedPts! > messagePts { + minValidatedPts = messagePts + } + invalidatedMessageIds.append(message.id) + } + } else { + invalidatedMessageIds.append(message.id) + } + } + default: + break + } + } + + if !invalidatedMessageIds.isEmpty { + let context: ChannelStateValidationContext + if let current = self.contexts[id] { + context = current + } else { + context = ChannelStateValidationContext() + self.contexts[id] = context + } + var messageIdsForBatch: [MessageId] = [] + for messageId in invalidatedMessageIds { + if let batch = context.batchReferences[messageId] { + if batch.invalidatedPts < invalidatedPts { + batch.cancelledMessageIds.insert(messageId) + messageIdsForBatch.append(messageId) + } + } else { + messageIdsForBatch.append(messageId) + } + } + if !messageIdsForBatch.isEmpty { + let disposable = MetaDisposable() + let batch = ChannelStateValidationBatch(disposable: disposable, invalidatedPts: invalidatedPts) + for messageId in messageIdsForBatch { + context.batchReferences[messageId] = batch + } + + disposable.set((validateBatch(postbox: self.postbox, network: self.network, messageIds: messageIdsForBatch, minValidatedPts: minValidatedPts) + |> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in + if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch { + var completedMessageIds: [MessageId] = [] + for (messageId, messageBatch) in context.batchReferences { + if messageBatch === batch { + completedMessageIds.append(messageId) + } + } + for messageId in completedMessageIds { + context.batchReferences.removeValue(forKey: messageId) + } + } + })) + } + } + + if let context = self.contexts[id] { + let messageIds = Set(invalidatedMessageIds) + var removeIds: [MessageId] = [] + + for batchMessageId in context.batchReferences.keys { + if !messageIds.contains(batchMessageId) { + removeIds.append(batchMessageId) + } + } + + for messageId in removeIds { + context.batchReferences.removeValue(forKey: messageId) + } + } + } + } else if self.contexts[id] != nil { + self.contexts.removeValue(forKey: id) + } + } +} + +private func validateBatch(postbox: Postbox, network: Network, messageIds: [MessageId], minValidatedPts: Int32?) -> Signal { + guard let peerId = messageIds.first?.peerId else { + return .never() + } + return postbox.modify { modifier -> Signal in + if let peer = modifier.getPeer(peerId), let inputChannel = apiInputChannel(peer) { + var ranges: [Api.MessageRange] = [] + var currentRange: (Int32, Int32)? + for id in messageIds.sorted() { + if let (minId, maxId) = currentRange { + if maxId == id.id - 1 { + currentRange = (minId, id.id) + } else { + ranges.append(Api.MessageRange.messageRange(minId: minId - 1, maxId: maxId + 1)) + currentRange = (id.id, id.id) + } + } else { + currentRange = (id.id, id.id) + } + } + if let (minId, maxId) = currentRange { + ranges.append(Api.MessageRange.messageRange(minId: minId - 1, maxId: maxId + 1)) + } + return network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilter(flags: 1 << 1, ranges: ranges), pts: minValidatedPts ?? 1, limit: 100)) + |> `catch` { _ -> Signal in + return .never() + } + |> mapToSignal { result -> Signal in + return postbox.modify { modifier -> Void in + let finalPts: Int32 + var deletedMessageIds: [MessageId] = [] + var updatedMessages: [MessageId: StoreMessage] = [:] + + var apiChats: [Api.Chat] = [] + var apiUsers: [Api.User] = [] + + switch result { + case let .channelDifference(_, pts, _, newMessages, otherUpdates, chats, users): + finalPts = pts + apiChats = chats + apiUsers = users + + for message in newMessages { + if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id { + updatedMessages[id] = message + } + } + for update in otherUpdates { + switch update { + case let .updateDeleteChannelMessages(_, messages, _, _): + for messageId in messages { + deletedMessageIds.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId)) + } + case let .updateNewChannelMessage(message, _, _): + if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id { + updatedMessages[id] = message + } + case let .updateEditChannelMessage(message, _, _): + if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id { + updatedMessages[id] = message + } + default: + break + } + } + case let .channelDifferenceEmpty(_, pts, _): + finalPts = pts + case let .channelDifferenceTooLong(_, pts, _, _, _, _, _, _, _, _): + finalPts = pts + } + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + for chat in apiChats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in apiUsers { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in + return updated + }) + modifier.updatePeerPresences(peerPresences) + + if !deletedMessageIds.isEmpty { + modifier.deleteMessages(deletedMessageIds) + } + + for (messageId, message) in updatedMessages { + modifier.updateMessage(messageId, update: { _ in + var attributes = message.attributes + for j in 0 ..< attributes.count { + if let _ = attributes[j] as? ChannelMessageStateVersionAttribute { + attributes.remove(at: j) + break + } + } + attributes.append(ChannelMessageStateVersionAttribute(pts: finalPts)) + return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: attributes, media: message.media)) + }) + } + + for messageId in messageIds { + if updatedMessages[messageId] == nil { + modifier.updateMessage(messageId, 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 attributes = currentMessage.attributes + for j in 0 ..< attributes.count { + if let _ = attributes[j] as? ChannelMessageStateVersionAttribute { + attributes.remove(at: j) + break + } + } + attributes.append(ChannelMessageStateVersionAttribute(pts: finalPts)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + } + } + } + } else { + return .never() + } + } |> switchToLatest +} diff --git a/TelegramCore/Holes.swift b/TelegramCore/Holes.swift index 2b27ae2877..80f8c54599 100644 --- a/TelegramCore/Holes.swift +++ b/TelegramCore/Holes.swift @@ -80,6 +80,7 @@ func fetchMessageHistoryHole(network: Network, postbox: Postbox, hole: MessageHi let messages: [Api.Message] let chats: [Api.Chat] let users: [Api.User] + var channelPts: Int32? switch result { case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers): messages = apiMessages @@ -89,17 +90,24 @@ func fetchMessageHistoryHole(network: Network, postbox: Postbox, hole: MessageHi messages = apiMessages chats = apiChats users = apiUsers - case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): + case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers): messages = apiMessages chats = apiChats users = apiUsers + channelPts = pts } return postbox.modify { modifier in var storeMessages: [StoreMessage] = [] for message in messages { if let storeMessage = StoreMessage(apiMessage: message) { - storeMessages.append(storeMessage) + if let channelPts = channelPts { + var attributes = storeMessage.attributes + attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts)) + storeMessages.append(storeMessage.withUpdatedAttributes(attributes)) + } else { + storeMessages.append(storeMessage) + } } } @@ -218,7 +226,7 @@ func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) - readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) if let apiChannelPts = apiChannelPts { - chatStates[peerId] = ChannelState(pts: apiChannelPts) + chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil) } notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) @@ -340,7 +348,7 @@ func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) - readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) if let apiChannelPts = apiChannelPts { - chatStates[peerId] = ChannelState(pts: apiChannelPts) + chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil) } notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) @@ -391,7 +399,15 @@ func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) - modifier.resetIncomingReadStates(readStates) for (peerId, chatState) in chatStates { - modifier.setPeerChatState(peerId, state: chatState) + if let chatState = chatState as? ChannelState { + if let current = modifier.getPeerChatState(peerId) as? ChannelState { + modifier.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts)) + } else { + modifier.setPeerChatState(peerId, state: chatState) + } + } else { + modifier.setPeerChatState(peerId, state: chatState) + } } if let replacePinnedPeerIds = replacePinnedPeerIds { diff --git a/TelegramCore/InstantPage.swift b/TelegramCore/InstantPage.swift index e227168ab1..1fcd3cd62c 100644 --- a/TelegramCore/InstantPage.swift +++ b/TelegramCore/InstantPage.swift @@ -28,6 +28,7 @@ private enum InstantPageBlockType: Int32 { case collage = 19 case slideshow = 20 case channelBanner = 21 + case audio = 22 } public indirect enum InstantPageBlock: Coding, Equatable { @@ -47,8 +48,9 @@ public indirect enum InstantPageBlock: Coding, Equatable { case pullQuote(text: RichText, caption: RichText) case image(id: MediaId, caption: RichText) case video(id: MediaId, caption: RichText, autoplay: Bool, loop: Bool) + case audio(id: MediaId, caption: RichText) case cover(InstantPageBlock) - case webEmbed(url: String?, html: String?, dimensions: CGSize, caption: RichText, stretchToWidth: Bool, allowScrolling: Bool) + case webEmbed(url: String?, html: String?, dimensions: CGSize, caption: RichText, stretchToWidth: Bool, allowScrolling: Bool, coverId: MediaId?) case postEmbed(url: String, webpageId: MediaId?, avatarId: MediaId?, author: String, date: Int32, blocks: [InstantPageBlock], caption: RichText) case collage(items: [InstantPageBlock], caption: RichText) case slideshow(items: [InstantPageBlock], caption: RichText) @@ -91,7 +93,11 @@ public indirect enum InstantPageBlock: Coding, Equatable { case InstantPageBlockType.cover.rawValue: self = .cover(decoder.decodeObjectForKey("c", decoder: { InstantPageBlock(decoder: $0) }) as! InstantPageBlock) case InstantPageBlockType.webEmbed.rawValue: - self = .webEmbed(url: decoder.decodeOptionalStringForKey("u"), html: decoder.decodeOptionalStringForKey("h"), dimensions: CGSize(width: CGFloat(decoder.decodeInt32ForKey("sw", orElse: 0)), height: CGFloat(decoder.decodeInt32ForKey("sh", orElse: 0))), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText, stretchToWidth: decoder.decodeInt32ForKey("st", orElse: 0) != 0, allowScrolling: decoder.decodeInt32ForKey("as", orElse: 0) != 0) + var coverId: MediaId? + if let coverIdNamespace = decoder.decodeOptionalInt32ForKey("ci.n"), let coverIdId = decoder.decodeOptionalInt64ForKey("ci.i") { + coverId = MediaId(namespace: coverIdNamespace, id: coverIdId) + } + self = .webEmbed(url: decoder.decodeOptionalStringForKey("u"), html: decoder.decodeOptionalStringForKey("h"), dimensions: CGSize(width: CGFloat(decoder.decodeInt32ForKey("sw", orElse: 0)), height: CGFloat(decoder.decodeInt32ForKey("sh", orElse: 0))), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText, stretchToWidth: decoder.decodeInt32ForKey("st", orElse: 0) != 0, allowScrolling: decoder.decodeInt32ForKey("as", orElse: 0) != 0, coverId: coverId) case InstantPageBlockType.postEmbed.rawValue: var avatarId: MediaId? let avatarIdNamespace: Int32? = decoder.decodeOptionalInt32ForKey("av.n") @@ -106,6 +112,8 @@ public indirect enum InstantPageBlock: Coding, Equatable { self = .slideshow(items: decoder.decodeObjectArrayWithDecoderForKey("b"), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText) case InstantPageBlockType.channelBanner.rawValue: self = .channelBanner(decoder.decodeObjectForKey("c") as? TelegramChannel) + case InstantPageBlockType.audio.rawValue: + self = .audio(id: MediaId(namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt64ForKey("i.i", orElse: 0)), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText) default: self = .unsupported } @@ -172,8 +180,15 @@ public indirect enum InstantPageBlock: Coding, Equatable { case let .cover(block): encoder.encodeInt32(InstantPageBlockType.cover.rawValue, forKey: "r") encoder.encodeObject(block, forKey: "c") - case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling): + case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling, coverId): encoder.encodeInt32(InstantPageBlockType.webEmbed.rawValue, forKey: "r") + if let coverId = coverId { + encoder.encodeInt32(coverId.namespace, forKey: "ci.n") + encoder.encodeInt64(coverId.id, forKey: "ci.i") + } else { + encoder.encodeNil(forKey: "ci.n") + encoder.encodeNil(forKey: "ci.i") + } if let url = url { encoder.encodeString(url, forKey: "u") } else { @@ -225,6 +240,11 @@ public indirect enum InstantPageBlock: Coding, Equatable { } else { encoder.encodeNil(forKey: "c") } + case let .audio(id, caption): + encoder.encodeInt32(InstantPageBlockType.audio.rawValue, forKey: "r") + encoder.encodeInt32(id.namespace, forKey: "i.n") + encoder.encodeInt64(id.id, forKey: "i.i") + encoder.encodeObject(caption, forKey: "c") } } @@ -332,8 +352,8 @@ public indirect enum InstantPageBlock: Coding, Equatable { } else { return false } - case let .webEmbed(lhsUrl, lhsHtml, lhsDimensions, lhsCaption, lhsStretchToWidth, lhsAllowScrolling): - if case let .webEmbed(rhsUrl, rhsHtml, rhsDimensions, rhsCaption, rhsStretchToWidth, rhsAllowScrolling) = rhs, lhsUrl == rhsUrl && lhsHtml == rhsHtml && lhsDimensions == rhsDimensions && lhsCaption == rhsCaption && lhsStretchToWidth == rhsStretchToWidth && lhsAllowScrolling == rhsAllowScrolling { + case let .webEmbed(lhsUrl, lhsHtml, lhsDimensions, lhsCaption, lhsStretchToWidth, lhsAllowScrolling, lhsCoverId): + if case let .webEmbed(rhsUrl, rhsHtml, rhsDimensions, rhsCaption, rhsStretchToWidth, rhsAllowScrolling, rhsCoverId) = rhs, lhsUrl == rhsUrl && lhsHtml == rhsHtml && lhsDimensions == rhsDimensions && lhsCaption == rhsCaption && lhsStretchToWidth == rhsStretchToWidth && lhsAllowScrolling == rhsAllowScrolling && lhsCoverId == rhsCoverId { return true } else { return false @@ -369,6 +389,12 @@ public indirect enum InstantPageBlock: Coding, Equatable { } else { return false } + case let .audio(id, caption): + if case .audio(id, caption) = rhs { + return true + } else { + return false + } } } } @@ -387,7 +413,9 @@ private final class MediaDictionary: Coding { var dict: [MediaId: Media] = [:] assert(mediaIds.count == medias.count) for i in 0 ..< mediaIds.count { - dict[mediaIds[i]] = medias[i] as! Media + if let media = medias[i] as? Media { + dict[mediaIds[i]] = media + } } self.dict = dict } @@ -489,11 +517,11 @@ extension InstantPageBlock { case let .pageBlockPhoto(photoId, caption): self = .image(id: MediaId(namespace: Namespaces.Media.CloudImage, id: photoId), caption: RichText(apiText: caption)) case let .pageBlockVideo(flags, videoId, caption): - self = .video(id: MediaId(namespace: Namespaces.Media.CloudVideo, id: videoId), caption: RichText(apiText: caption), autoplay: (flags & (1 << 0)) != 0, loop: (flags & (1 << 1)) != 0) + self = .video(id: MediaId(namespace: Namespaces.Media.CloudFile, id: videoId), caption: RichText(apiText: caption), autoplay: (flags & (1 << 0)) != 0, loop: (flags & (1 << 1)) != 0) case let .pageBlockCover(cover): self = .cover(InstantPageBlock(apiBlock: cover)) case let .pageBlockEmbed(flags, url, html, posterPhotoId, w, h, caption): - self = .webEmbed(url: url, html: html, dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), caption: RichText(apiText: caption), stretchToWidth: (flags & (1 << 0)) != 0, allowScrolling: (flags & (1 << 3)) != 0) + self = .webEmbed(url: url, html: html, dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), caption: RichText(apiText: caption), stretchToWidth: (flags & (1 << 0)) != 0, allowScrolling: (flags & (1 << 3)) != 0, coverId: posterPhotoId.flatMap { MediaId(namespace: Namespaces.Media.CloudImage, id: $0) }) case let .pageBlockEmbedPost(url, webpageId, authorPhotoId, author, date, blocks, caption): self = .postEmbed(url: url, webpageId: webpageId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId), avatarId: authorPhotoId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudImage, id: authorPhotoId), author: author, date: date, blocks: blocks.map({ InstantPageBlock(apiBlock: $0) }), caption: RichText(apiText: caption)) case let .pageBlockCollage(items, caption): @@ -502,6 +530,8 @@ extension InstantPageBlock { self = .slideshow(items: items.map({ InstantPageBlock(apiBlock: $0) }), caption: RichText(apiText: caption)) case let .pageBlockChannel(channel: apiChat): self = .channelBanner(parseTelegramGroupOrChannel(chat: apiChat) as? TelegramChannel) + case let .pageBlockAudio(audioId, caption): + self = .audio(id: MediaId(namespace: Namespaces.Media.CloudFile, id: audioId), caption: RichText(apiText: caption)) } } } diff --git a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift index 689856c602..c8b28bc06d 100644 --- a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift +++ b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift @@ -178,7 +178,7 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) if let apiChannelPts = apiChannelPts { - chatStates[peerId] = ChannelState(pts: apiChannelPts) + chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil) } notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) @@ -234,7 +234,15 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ modifier.resetIncomingReadStates(readStates) for (peerId, chatState) in chatStates { - modifier.setPeerChatState(peerId, state: chatState) + if let chatState = chatState as? ChannelState { + if let current = modifier.getPeerChatState(peerId) as? ChannelState { + modifier.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts)) + } else { + modifier.setPeerChatState(peerId, state: chatState) + } + } else { + modifier.setPeerChatState(peerId, state: chatState) + } } if remotePeerIds == resultingPeerIds { diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index e1f3ca0d74..34cb437737 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -14,7 +14,6 @@ public struct Namespaces { public struct Media { public static let CloudImage: Int32 = 0 - public static let CloudVideo: Int32 = 1 public static let CloudAudio: Int32 = 2 public static let CloudContact: Int32 = 3 public static let CloudMap: Int32 = 4 diff --git a/TelegramCore/OutgoingMessageWithChatContextResult.swift b/TelegramCore/OutgoingMessageWithChatContextResult.swift index dfae67e4b6..45973945a5 100644 --- a/TelegramCore/OutgoingMessageWithChatContextResult.swift +++ b/TelegramCore/OutgoingMessageWithChatContextResult.swift @@ -128,7 +128,9 @@ public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCol } switch result { case let .internalReference(id, type, title, description, image, file, message): - if let image = image { + if type == "game" { + return .message(text: "", attributes: attributes, media: TelegramMediaGame(gameId: 0, accessHash: 0, name: "", title: title ?? "", description: description ?? "", image: image, file: file), replyToMessageId: nil) + } else if let image = image { return .message(text: caption, attributes: attributes, media: image, replyToMessageId: nil) } else if let file = file { return .message(text: caption, attributes: attributes, media: file, replyToMessageId: nil) diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index 0effd31ef9..cc6eb51f92 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -268,8 +268,15 @@ func textAndMediaFromApiMedia(_ media: Api.MessageMedia?, _ peerId:PeerId) -> (S break case let .messageMediaGame(game): return (nil, TelegramMediaGame(apiGame: game)) - case let .messageMediaInvoice(_, title, description, photo, receiptMsgId, currency, totalAmount, startParam): - return (nil, TelegramMediaInvoice(title: title, description: description, photo: photo != nil ? TelegramMediaWebFile(photo!) : nil, receiptMessageId: receiptMsgId != nil ? MessageId(peerId: peerId, namespace: 0, id: receiptMsgId!) : nil, currency: currency, totalAmount: totalAmount, startParam: startParam)) + case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam): + var parsedFlags = TelegramMediaInvoiceFlags() + if (flags & (1 << 3)) != 0 { + parsedFlags.insert(.isTest) + } + if (flags & (1 << 1)) != 0 { + parsedFlags.insert(.shippingAddressRequested) + } + return (nil, TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, flags: parsedFlags)) } } diff --git a/TelegramCore/SynchronizePeerReadState.swift b/TelegramCore/SynchronizePeerReadState.swift index 6ef54d82f9..82eca32cea 100644 --- a/TelegramCore/SynchronizePeerReadState.swift +++ b/TelegramCore/SynchronizePeerReadState.swift @@ -63,6 +63,35 @@ private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId } } +func fetchPeerCloudReadState(network: Network, postbox: Postbox, peerId: PeerId, inputPeer: Api.InputPeer) -> Signal { + return network.request(Api.functions.messages.getPeerDialogs(peers: [inputPeer])) + |> map { result -> PeerReadState? in + switch result { + case let .peerDialogs(dialogs, _, _, _, _): + if let dialog = dialogs.filter({ $0.peerId == peerId }).first { + let apiTopMessage: Int32 + let apiReadInboxMaxId: Int32 + let apiReadOutboxMaxId: Int32 + let apiUnreadCount: Int32 + switch dialog { + case let .dialog(_, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, _): + apiTopMessage = topMessage + apiReadInboxMaxId = readInboxMaxId + apiReadOutboxMaxId = readOutboxMaxId + apiUnreadCount = unreadCount + } + + return .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) + } else { + return nil + } + } + } + |> `catch` { _ -> Signal in + return .single(nil) + } +} + private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker), VerifyReadStateError> { return dialogTopMessage(network: network, postbox: postbox, peerId: peerId) |> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker), VerifyReadStateError> in diff --git a/TelegramCore/TelegramMediaFile.swift b/TelegramCore/TelegramMediaFile.swift index 3f4a8cf8c0..f0a61c7072 100644 --- a/TelegramCore/TelegramMediaFile.swift +++ b/TelegramCore/TelegramMediaFile.swift @@ -136,6 +136,20 @@ public enum TelegramMediaFileAttribute: Coding { } } +func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> CGSize? { + for attribute in attributes { + switch attribute { + case let .Video(_, size, _): + return size + case let .ImageSize(size): + return size + default: + break + } + } + return nil +} + public final class TelegramMediaFile: Media, Equatable { public let fileId: MediaId public let resource: TelegramMediaResource @@ -255,17 +269,7 @@ public final class TelegramMediaFile: Media, Equatable { } public var dimensions: CGSize? { - for attribute in self.attributes { - switch attribute { - case let .Video(_, size, _): - return size - case let .ImageSize(size): - return size - default: - break - } - } - return nil + return dimensionsForFileAttributes(self.attributes) } public func isEqual(_ other: Media) -> Bool { diff --git a/TelegramCore/TelegramMediaInvoice.swift b/TelegramCore/TelegramMediaInvoice.swift index 5c98f1b09d..1be1dc63a9 100644 --- a/TelegramCore/TelegramMediaInvoice.swift +++ b/TelegramCore/TelegramMediaInvoice.swift @@ -5,6 +5,21 @@ import Foundation import Postbox #endif +public struct TelegramMediaInvoiceFlags: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let isTest = TelegramMediaInvoiceFlags(rawValue: 1 << 0) + public static let shippingAddressRequested = TelegramMediaInvoiceFlags(rawValue: 1 << 1) +} + //flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String //messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; @@ -13,16 +28,16 @@ public final class TelegramMediaInvoice: Media { public var id: MediaId? = nil - public let title:String - public let description:String - // public let photo: - public let receiptMessageId:MessageId? - public let currency:String - public let totalAmount:Int64 - public let startParam:String - public let photo:TelegramMediaWebFile? + public let title: String + public let description: String + public let receiptMessageId: MessageId? + public let currency: String + public let totalAmount: Int64 + public let startParam: String + public let photo: TelegramMediaWebFile? + public let flags: TelegramMediaInvoiceFlags - public init(title:String, description:String, photo:TelegramMediaWebFile?, receiptMessageId:MessageId?, currency:String, totalAmount:Int64, startParam:String) { + public init(title: String, description: String, photo: TelegramMediaWebFile?, receiptMessageId: MessageId?, currency: String, totalAmount: Int64, startParam: String, flags: TelegramMediaInvoiceFlags) { self.title = title self.description = description self.photo = photo @@ -30,9 +45,9 @@ public final class TelegramMediaInvoice: Media { self.currency = currency self.totalAmount = totalAmount self.startParam = startParam + self.flags = flags } - public init(decoder: Decoder) { self.title = decoder.decodeStringForKey("t", orElse: "") self.description = decoder.decodeStringForKey("d", orElse: "") @@ -40,6 +55,7 @@ public final class TelegramMediaInvoice: Media { self.totalAmount = decoder.decodeInt64ForKey("ta", orElse: 0) self.startParam = decoder.decodeStringForKey("sp", orElse: "") self.photo = decoder.decodeObjectForKey("p") as? TelegramMediaWebFile + self.flags = TelegramMediaInvoiceFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)) if let receiptMessageIdPeerId = decoder.decodeOptionalInt64ForKey("r.p") as Int64?, let receiptMessageIdNamespace = decoder.decodeOptionalInt32ForKey("r.n") as Int32?, let receiptMessageIdId = decoder.decodeOptionalInt32ForKey("r.i") as Int32? { self.receiptMessageId = MessageId(peerId: PeerId(receiptMessageIdPeerId), namespace: receiptMessageIdNamespace, id: receiptMessageIdId) @@ -54,6 +70,7 @@ public final class TelegramMediaInvoice: Media { encoder.encodeString(self.currency, forKey: "nc") encoder.encodeInt64(self.totalAmount, forKey: "ta") encoder.encodeString(self.startParam, forKey: "sp") + encoder.encodeInt32(self.flags.rawValue, forKey: "f") if let photo = photo { encoder.encodeObject(photo, forKey: "p") @@ -101,6 +118,10 @@ public final class TelegramMediaInvoice: Media { return false } + if self.flags != other.flags { + return false + } + return true } diff --git a/TelegramCore/TelegramMediaWebDocument.swift b/TelegramCore/TelegramMediaWebDocument.swift index 5bd927ea1a..a9292080d5 100644 --- a/TelegramCore/TelegramMediaWebDocument.swift +++ b/TelegramCore/TelegramMediaWebDocument.swift @@ -6,10 +6,6 @@ import Foundation #endif public class TelegramMediaWebFile: Media { - - - - public let resource: TelegramMediaResource public let mimeType: String public let size: Int32 @@ -61,14 +57,16 @@ public class TelegramMediaWebFile: Media { return true } - + public var dimensions: CGSize? { + return dimensionsForFileAttributes(self.attributes) + } } extension TelegramMediaWebFile { convenience init(_ document:Api.WebDocument) { switch document { - case let .webDocument(data): - self.init(resource: WebFileReferenceMediaResource(url: data.url, size: data.size, datacenterId: data.dcId, accessHash: data.accessHash), mimeType: data.mimeType, size: data.size, attributes: telegramMediaFileAttributesFromApiAttributes(data.attributes)) + case let .webDocument(data): + self.init(resource: WebFileReferenceMediaResource(url: data.url, size: data.size, datacenterId: data.dcId, accessHash: data.accessHash), mimeType: data.mimeType, size: data.size, attributes: telegramMediaFileAttributesFromApiAttributes(data.attributes)) } } }