mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-20 12:36:00 +00:00
Merge branch 'group_calls' of https://github.com/peter-iakovlev/TelegramCoreDev into group_calls
This commit is contained in:
commit
c353091c51
@ -32,8 +32,6 @@
|
|||||||
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
|
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
|
||||||
C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; };
|
C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; };
|
||||||
C27982511E72C97800262BFD /* MacosLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982501E72C97800262BFD /* MacosLegacy.swift */; };
|
C27982511E72C97800262BFD /* MacosLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982501E72C97800262BFD /* MacosLegacy.swift */; };
|
||||||
C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982521E73077800262BFD /* SecretChatStateBridge.swift */; };
|
|
||||||
C27982541E73078400262BFD /* SecretChatStateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982521E73077800262BFD /* SecretChatStateBridge.swift */; };
|
|
||||||
C28725421EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
|
C28725421EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
|
||||||
C28725431EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
|
C28725431EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
|
||||||
C29340F31F5080FA0074991E /* UpdateGroupSpecificStickerset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */; };
|
C29340F31F5080FA0074991E /* UpdateGroupSpecificStickerset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */; };
|
||||||
@ -103,6 +101,11 @@
|
|||||||
D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; };
|
D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; };
|
||||||
D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
|
D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
|
||||||
D018D3381E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
|
D018D3381E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
|
||||||
|
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */; };
|
||||||
|
D018EE0220458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; };
|
||||||
|
D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; };
|
||||||
|
D018EE052045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */; };
|
||||||
|
D018EE062045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */; };
|
||||||
D019B1CC1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
|
D019B1CC1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
|
||||||
D019B1CD1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
|
D019B1CD1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
|
||||||
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */; };
|
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */; };
|
||||||
@ -556,6 +559,8 @@
|
|||||||
D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; };
|
D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; };
|
||||||
D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
|
D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
|
||||||
D0E8174A2010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
|
D0E8174A2010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
|
||||||
|
D0E8B8B32044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; };
|
||||||
|
D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; };
|
||||||
D0F02CE51E9926C40065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
|
D0F02CE51E9926C40065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
|
||||||
D0F02CE61E9926C50065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
|
D0F02CE61E9926C50065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
|
||||||
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; };
|
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; };
|
||||||
@ -588,6 +593,8 @@
|
|||||||
D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; };
|
D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; };
|
||||||
D0F8C3A02017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
|
D0F8C3A02017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
|
||||||
D0F8C3A12017AF2700236FC5 /* 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 */; };
|
D0FA0ABD1E76C908005BB9B7 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
|
||||||
D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
|
D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
|
||||||
D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
|
D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
|
||||||
@ -644,7 +651,6 @@
|
|||||||
C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportContact.swift; sourceTree = "<group>"; };
|
C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportContact.swift; sourceTree = "<group>"; };
|
||||||
C251D7421E65E50500283EDE /* StickerSetInstallation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerSetInstallation.swift; sourceTree = "<group>"; };
|
C251D7421E65E50500283EDE /* StickerSetInstallation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerSetInstallation.swift; sourceTree = "<group>"; };
|
||||||
C27982501E72C97800262BFD /* MacosLegacy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacosLegacy.swift; sourceTree = "<group>"; };
|
C27982501E72C97800262BFD /* MacosLegacy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacosLegacy.swift; sourceTree = "<group>"; };
|
||||||
C27982521E73077800262BFD /* SecretChatStateBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatStateBridge.swift; sourceTree = "<group>"; };
|
|
||||||
C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfoMessageAttribute.swift; sourceTree = "<group>"; };
|
C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfoMessageAttribute.swift; sourceTree = "<group>"; };
|
||||||
C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroupSpecificStickerset.swift; sourceTree = "<group>"; };
|
C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroupSpecificStickerset.swift; sourceTree = "<group>"; };
|
||||||
C2E064671ECEEF0A00387BB8 /* TelegramMediaInvoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaInvoice.swift; sourceTree = "<group>"; };
|
C2E064671ECEEF0A00387BB8 /* TelegramMediaInvoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaInvoice.swift; sourceTree = "<group>"; };
|
||||||
@ -674,6 +680,9 @@
|
|||||||
D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = "<group>"; };
|
D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = "<group>"; };
|
||||||
D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = "<group>"; };
|
D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = "<group>"; };
|
||||||
D018D3361E648ACF00C5E089 /* CreateChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateChannel.swift; sourceTree = "<group>"; };
|
D018D3361E648ACF00C5E089 /* CreateChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateChannel.swift; sourceTree = "<group>"; };
|
||||||
|
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer73.swift; sourceTree = "<group>"; };
|
||||||
|
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretChatLayerNegotiation.swift; sourceTree = "<group>"; };
|
||||||
|
D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CkeckPeerChatServiceActions.swift; sourceTree = "<group>"; };
|
||||||
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatRekeySession.swift; sourceTree = "<group>"; };
|
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatRekeySession.swift; sourceTree = "<group>"; };
|
||||||
D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedStickersOperation.swift; sourceTree = "<group>"; };
|
D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedStickersOperation.swift; sourceTree = "<group>"; };
|
||||||
D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedStickersOperations.swift; sourceTree = "<group>"; };
|
D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedStickersOperations.swift; sourceTree = "<group>"; };
|
||||||
@ -938,6 +947,7 @@
|
|||||||
D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = "<group>"; };
|
D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = "<group>"; };
|
||||||
D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = "<group>"; };
|
D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = "<group>"; };
|
||||||
D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAdminEventLogContext.swift; sourceTree = "<group>"; };
|
D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAdminEventLogContext.swift; sourceTree = "<group>"; };
|
||||||
|
D0E8B8B22044706300605593 /* ForwardGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardGame.swift; sourceTree = "<group>"; };
|
||||||
D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConfigurationUpdates.swift; sourceTree = "<group>"; };
|
D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConfigurationUpdates.swift; sourceTree = "<group>"; };
|
||||||
D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = "<group>"; };
|
D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = "<group>"; };
|
||||||
D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeChatInputStateOperations.swift; sourceTree = "<group>"; };
|
D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeChatInputStateOperations.swift; sourceTree = "<group>"; };
|
||||||
@ -949,6 +959,7 @@
|
|||||||
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = "<group>"; };
|
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = "<group>"; };
|
||||||
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFeedPeers.swift; sourceTree = "<group>"; };
|
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFeedPeers.swift; sourceTree = "<group>"; };
|
||||||
D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = "<group>"; };
|
D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = "<group>"; };
|
||||||
D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = "<group>"; };
|
D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = "<group>"; };
|
||||||
D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = "<group>"; };
|
D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = "<group>"; };
|
||||||
D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = "<group>"; };
|
D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = "<group>"; };
|
||||||
@ -1020,6 +1031,7 @@
|
|||||||
D01C7ED51EF5E468008305F1 /* ProxySettings.swift */,
|
D01C7ED51EF5E468008305F1 /* ProxySettings.swift */,
|
||||||
D0B167221F9F972E00976B40 /* LoggingSettings.swift */,
|
D0B167221F9F972E00976B40 /* LoggingSettings.swift */,
|
||||||
D0AF32301FACEDEC0097362B /* CoreSettings.swift */,
|
D0AF32301FACEDEC0097362B /* CoreSettings.swift */,
|
||||||
|
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */,
|
||||||
);
|
);
|
||||||
name = Settings;
|
name = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1300,6 +1312,7 @@
|
|||||||
children = (
|
children = (
|
||||||
D0FA8BB51E223C16001E855B /* SecretApiLayer8.swift */,
|
D0FA8BB51E223C16001E855B /* SecretApiLayer8.swift */,
|
||||||
D0448C981E268F9A005A61A7 /* SecretApiLayer46.swift */,
|
D0448C981E268F9A005A61A7 /* SecretApiLayer46.swift */,
|
||||||
|
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */,
|
||||||
D03B0D541D631A6900955575 /* Api.swift */,
|
D03B0D541D631A6900955575 /* Api.swift */,
|
||||||
D03B0D551D631A6900955575 /* Buffer.swift */,
|
D03B0D551D631A6900955575 /* Buffer.swift */,
|
||||||
D03B0D561D631A6900955575 /* Download.swift */,
|
D03B0D561D631A6900955575 /* Download.swift */,
|
||||||
@ -1364,6 +1377,7 @@
|
|||||||
D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */,
|
D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */,
|
||||||
D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */,
|
D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */,
|
||||||
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */,
|
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */,
|
||||||
|
D0E8B8B22044706300605593 /* ForwardGame.swift */,
|
||||||
);
|
);
|
||||||
name = Messages;
|
name = Messages;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1595,6 +1609,7 @@
|
|||||||
D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */,
|
D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */,
|
||||||
D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */,
|
D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */,
|
||||||
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */,
|
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */,
|
||||||
|
D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */,
|
||||||
);
|
);
|
||||||
name = Peers;
|
name = Peers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1615,7 +1630,7 @@
|
|||||||
D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */,
|
D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */,
|
||||||
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */,
|
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */,
|
||||||
D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */,
|
D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */,
|
||||||
C27982521E73077800262BFD /* SecretChatStateBridge.swift */,
|
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */,
|
||||||
);
|
);
|
||||||
name = "Secret Chats";
|
name = "Secret Chats";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1827,11 +1842,13 @@
|
|||||||
D03B0CBD1D62234300955575 /* Regex.swift in Sources */,
|
D03B0CBD1D62234300955575 /* Regex.swift in Sources */,
|
||||||
D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */,
|
D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */,
|
||||||
D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */,
|
D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */,
|
||||||
|
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */,
|
||||||
D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */,
|
D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */,
|
||||||
D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */,
|
D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */,
|
||||||
D053B4181F18DE4F00E2D58A /* AuthorSignatureMessageAttribute.swift in Sources */,
|
D053B4181F18DE4F00E2D58A /* AuthorSignatureMessageAttribute.swift in Sources */,
|
||||||
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */,
|
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */,
|
||||||
D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */,
|
D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */,
|
||||||
|
D0E8B8B32044706300605593 /* ForwardGame.swift in Sources */,
|
||||||
D01A21AC1F38D10E00DDA104 /* SavedStickerItem.swift in Sources */,
|
D01A21AC1F38D10E00DDA104 /* SavedStickerItem.swift in Sources */,
|
||||||
D0642EF91F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift in Sources */,
|
D0642EF91F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift in Sources */,
|
||||||
D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */,
|
D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */,
|
||||||
@ -2042,6 +2059,7 @@
|
|||||||
D05A32E41E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */,
|
D05A32E41E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */,
|
||||||
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */,
|
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */,
|
||||||
D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */,
|
D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */,
|
||||||
|
D018EE052045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */,
|
||||||
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
|
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
|
||||||
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
|
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
|
||||||
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
|
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
|
||||||
@ -2053,18 +2071,19 @@
|
|||||||
D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */,
|
D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */,
|
||||||
D03B0CE21D62249B00955575 /* InlineBotMessageAttribute.swift in Sources */,
|
D03B0CE21D62249B00955575 /* InlineBotMessageAttribute.swift in Sources */,
|
||||||
D0AB0B9A1D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift in Sources */,
|
D0AB0B9A1D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift in Sources */,
|
||||||
C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */,
|
|
||||||
D03B0D5B1D631A6900955575 /* Buffer.swift in Sources */,
|
D03B0D5B1D631A6900955575 /* Buffer.swift in Sources */,
|
||||||
D0B843891DA7AB96005F29E1 /* ExportedInvitation.swift in Sources */,
|
D0B843891DA7AB96005F29E1 /* ExportedInvitation.swift in Sources */,
|
||||||
D03B0E441D631E6600955575 /* NetworkLogging.m in Sources */,
|
D03B0E441D631E6600955575 /* NetworkLogging.m in Sources */,
|
||||||
D0528E651E65C82400E2FEF5 /* UpdateContactName.swift in Sources */,
|
D0528E651E65C82400E2FEF5 /* UpdateContactName.swift in Sources */,
|
||||||
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */,
|
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */,
|
||||||
|
D018EE0220458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */,
|
||||||
D0C48F391E8138DF0075317D /* ArchivedStickerPacksInfo.swift in Sources */,
|
D0C48F391E8138DF0075317D /* ArchivedStickerPacksInfo.swift in Sources */,
|
||||||
C239BE971E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift in Sources */,
|
C239BE971E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift in Sources */,
|
||||||
D03B0CC11D62235000955575 /* StringFormat.swift in Sources */,
|
D03B0CC11D62235000955575 /* StringFormat.swift in Sources */,
|
||||||
D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */,
|
D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */,
|
||||||
D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
|
D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
|
||||||
C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */,
|
C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */,
|
||||||
|
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
|
||||||
D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */,
|
D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */,
|
||||||
D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */,
|
D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */,
|
||||||
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
|
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
|
||||||
@ -2106,7 +2125,6 @@
|
|||||||
files = (
|
files = (
|
||||||
C29340F41F5081280074991E /* UpdateGroupSpecificStickerset.swift in Sources */,
|
C29340F41F5081280074991E /* UpdateGroupSpecificStickerset.swift in Sources */,
|
||||||
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */,
|
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */,
|
||||||
C27982541E73078400262BFD /* SecretChatStateBridge.swift in Sources */,
|
|
||||||
D00DBBD81E64E41100DB5485 /* CreateSecretChat.swift in Sources */,
|
D00DBBD81E64E41100DB5485 /* CreateSecretChat.swift in Sources */,
|
||||||
C2FD33EC1E696C79008D13D4 /* GroupsInCommon.swift in Sources */,
|
C2FD33EC1E696C79008D13D4 /* GroupsInCommon.swift in Sources */,
|
||||||
C239BE9D1E630CB300C2C453 /* UpdatePinnedMessage.swift in Sources */,
|
C239BE9D1E630CB300C2C453 /* UpdatePinnedMessage.swift in Sources */,
|
||||||
@ -2144,6 +2162,7 @@
|
|||||||
D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */,
|
D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */,
|
||||||
D0E23DE01E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */,
|
D0E23DE01E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */,
|
||||||
D050F2521E4A59C200988324 /* JoinLink.swift in Sources */,
|
D050F2521E4A59C200988324 /* JoinLink.swift in Sources */,
|
||||||
|
D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */,
|
||||||
D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */,
|
D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */,
|
||||||
D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */,
|
D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */,
|
||||||
D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */,
|
D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */,
|
||||||
@ -2215,6 +2234,7 @@
|
|||||||
D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */,
|
D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */,
|
||||||
D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */,
|
D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */,
|
||||||
D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */,
|
D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */,
|
||||||
|
D018EE062045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */,
|
||||||
D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */,
|
D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */,
|
||||||
D0C27B431F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */,
|
D0C27B431F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */,
|
||||||
D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */,
|
D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */,
|
||||||
@ -2283,6 +2303,7 @@
|
|||||||
C210DD631FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift in Sources */,
|
C210DD631FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift in Sources */,
|
||||||
D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */,
|
D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */,
|
||||||
D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */,
|
D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */,
|
||||||
|
D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
|
||||||
D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */,
|
D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */,
|
||||||
D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */,
|
D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */,
|
||||||
D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */,
|
D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */,
|
||||||
@ -2292,6 +2313,7 @@
|
|||||||
D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */,
|
D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */,
|
||||||
D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */,
|
D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */,
|
||||||
D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */,
|
D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */,
|
||||||
|
D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */,
|
||||||
D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */,
|
D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */,
|
||||||
D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
|
D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
|
||||||
D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */,
|
D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */,
|
||||||
|
@ -253,6 +253,7 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) })
|
declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) })
|
||||||
declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) })
|
declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) })
|
||||||
declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) })
|
declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) })
|
||||||
|
declareEncodable(ContentPrivacySettings.self, f: { ContentPrivacySettings(decoder: $0) })
|
||||||
|
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
@ -701,7 +701,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
|
|||||||
case let .webPageEmpty(id):
|
case let .webPageEmpty(id):
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -838,7 +838,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
|
|||||||
case let .webPageEmpty(id):
|
case let .webPageEmpty(id):
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1539,7 +1539,7 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt
|
|||||||
case let .webPageEmpty(id):
|
case let .webPageEmpty(id):
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1975,7 +1975,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif
|
|||||||
peerIdsWithAddedSecretMessages.insert(peerId)
|
peerIdsWithAddedSecretMessages.insert(peerId)
|
||||||
}
|
}
|
||||||
case let .ReadSecretOutbox(peerId, maxTimestamp, actionTimestamp):
|
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):
|
case let .AddPeerInputActivity(chatPeerId, peerId, activity):
|
||||||
if let peerId = peerId {
|
if let peerId = peerId {
|
||||||
if updatedTypingActivities[chatPeerId] == nil {
|
if updatedTypingActivities[chatPeerId] == nil {
|
||||||
|
@ -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)
|
let messageIds = modifier.applyOutgoingReadMaxIndex(index)
|
||||||
if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
for id in messageIds {
|
for id in messageIds {
|
||||||
if let message = modifier.getMessage(id), !message.flags.contains(.Incoming) {
|
applySecretOutgoingMessageReadActions(modifier: modifier, id: id, beginCountdownAt: timestamp)
|
||||||
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?
|
func maybeReadSecretOutgoingMessage(modifier: Modifier, index: MessageIndex) {
|
||||||
if let forwardInfo = currentMessage.forwardInfo {
|
guard index.id.peerId.namespace == Namespaces.Peer.SecretChat else {
|
||||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
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)
|
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))
|
||||||
}
|
})
|
||||||
break
|
modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id)
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,10 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||||||
var sentStickers: [TelegramMediaFile] = []
|
var sentStickers: [TelegramMediaFile] = []
|
||||||
var sentGifs: [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
|
modifier.updateMessage(message.id, update: { currentMessage in
|
||||||
let updatedId: MessageId
|
let updatedId: MessageId
|
||||||
if let messageId = 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))
|
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 {
|
for file in sentStickers {
|
||||||
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
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?
|
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 {
|
for (message, _, updatedMessage) in mapping {
|
||||||
modifier.updateMessage(message.id, update: { currentMessage in
|
modifier.updateMessage(message.id, update: { currentMessage in
|
||||||
let updatedId: MessageId
|
let updatedId: MessageId
|
||||||
@ -259,10 +264,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
|
|||||||
modifier.updateMessageGroupingKeysAtomically(mapping.map { $0.1.id }, groupingKey: updatedGroupingKey)
|
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 {
|
for file in sentStickers {
|
||||||
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ public struct BotPaymentForm {
|
|||||||
public let canSaveCredentials: Bool
|
public let canSaveCredentials: Bool
|
||||||
public let passwordMissing: Bool
|
public let passwordMissing: Bool
|
||||||
public let invoice: BotPaymentInvoice
|
public let invoice: BotPaymentInvoice
|
||||||
public let providerId: Int32
|
public let providerId: PeerId
|
||||||
public let url: String
|
public let url: String
|
||||||
public let nativeProvider: BotPaymentNativeProvider?
|
public let nativeProvider: BotPaymentNativeProvider?
|
||||||
public let savedInfo: BotPaymentRequestedInfo?
|
public let savedInfo: BotPaymentRequestedInfo?
|
||||||
@ -229,7 +229,7 @@ public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: M
|
|||||||
parsedSavedCredentials = .card(id: id, title: 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)
|
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: providerId), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
|
||||||
}
|
}
|
||||||
} |> mapError { _ -> BotPaymentFormRequestError in return .generic }
|
} |> mapError { _ -> BotPaymentFormRequestError in return .generic }
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,47 @@ import Foundation
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public enum CallSessionError {
|
public enum CallSessionError: Equatable {
|
||||||
case generic
|
case generic
|
||||||
case privacyRestricted
|
case privacyRestricted
|
||||||
case notSupportedByPeer
|
case notSupportedByPeer
|
||||||
case serverProvided(String)
|
case serverProvided(String)
|
||||||
case disconnected
|
case disconnected
|
||||||
|
|
||||||
|
public static func ==(lhs: CallSessionError, rhs: CallSessionError) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case .generic:
|
||||||
|
if case .generic = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .privacyRestricted:
|
||||||
|
if case .privacyRestricted = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .notSupportedByPeer:
|
||||||
|
if case .notSupportedByPeer = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .serverProvided(text):
|
||||||
|
if case .serverProvided(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .disconnected:
|
||||||
|
if case .disconnected = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CallSessionEndedType {
|
public enum CallSessionEndedType {
|
||||||
@ -23,14 +58,31 @@ public enum CallSessionEndedType {
|
|||||||
case missed
|
case missed
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CallSessionTerminationReason {
|
public enum CallSessionTerminationReason: Equatable {
|
||||||
case ended(CallSessionEndedType)
|
case ended(CallSessionEndedType)
|
||||||
case error(CallSessionError)
|
case error(CallSessionError)
|
||||||
|
|
||||||
|
public static func ==(lhs: CallSessionTerminationReason, rhs: CallSessionTerminationReason) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .ended(type):
|
||||||
|
if case .ended(type) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .error(error):
|
||||||
|
if case .error(error) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ReportCallRating {
|
public struct ReportCallRating {
|
||||||
public let id:Int64
|
public let id: Int64
|
||||||
public let accessHash:Int64
|
public let accessHash: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CallSessionInternalState {
|
enum CallSessionInternalState {
|
||||||
@ -127,6 +179,8 @@ private final class CallSessionContext {
|
|||||||
var state: CallSessionInternalState
|
var state: CallSessionInternalState
|
||||||
let subscribers = Bag<(CallSession) -> Void>()
|
let subscribers = Bag<(CallSession) -> Void>()
|
||||||
|
|
||||||
|
let acknowledgeIncomingCallDisposable = MetaDisposable()
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
if case .terminated = self.state {
|
if case .terminated = self.state {
|
||||||
return self.subscribers.isEmpty
|
return self.subscribers.isEmpty
|
||||||
@ -140,6 +194,10 @@ private final class CallSessionContext {
|
|||||||
self.isOutgoing = isOutgoing
|
self.isOutgoing = isOutgoing
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.acknowledgeIncomingCallDisposable.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class CallSessionManagerContext {
|
private final class CallSessionManagerContext {
|
||||||
@ -252,7 +310,9 @@ private final class CallSessionManagerContext {
|
|||||||
|
|
||||||
if randomStatus == 0 {
|
if randomStatus == 0 {
|
||||||
let internalId = CallSessionInternalId()
|
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.contextIdByStableId[stableId] = internalId
|
||||||
self.contextUpdated(internalId: internalId)
|
self.contextUpdated(internalId: internalId)
|
||||||
self.ringingStatesUpdated()
|
self.ringingStatesUpdated()
|
||||||
|
21
TelegramCore/CkeckPeerChatServiceActions.swift
Normal file
21
TelegramCore/CkeckPeerChatServiceActions.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Foundation
|
||||||
|
#if os(macOS)
|
||||||
|
import PostboxMac
|
||||||
|
import SwiftSignalKitMac
|
||||||
|
#else
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
|
||||||
|
return postbox.modify { modifier -> Void in
|
||||||
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
if let state = modifier.getPeerChatState(peerId) as? SecretChatState {
|
||||||
|
let updatedState = secretChatCheckLayerNegotiationIfNeeded(modifier: modifier, peerId: peerId, state: state)
|
||||||
|
if state != updatedState {
|
||||||
|
modifier.setPeerChatState(peerId, state: updatedState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
TelegramCore/ContentPrivacySettings.swift
Normal file
66
TelegramCore/ContentPrivacySettings.swift
Normal file
@ -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<Void, NoError> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,7 @@ public func deleteMessagesInteractively(postbox: Postbox, messageIds: [MessageId
|
|||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
layer = .layer8
|
layer = .layer8
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||||
}
|
}
|
||||||
if let layer = layer {
|
if let layer = layer {
|
||||||
var globallyUniqueIds: [Int64] = []
|
var globallyUniqueIds: [Int64] = []
|
||||||
@ -84,7 +84,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId) -> Signa
|
|||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
layer = .layer8
|
layer = .layer8
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
if let layer = layer {
|
if let layer = layer {
|
||||||
|
27
TelegramCore/ForwardGame.swift
Normal file
27
TelegramCore/ForwardGame.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import Foundation
|
||||||
|
#if os(macOS)
|
||||||
|
import PostboxMac
|
||||||
|
import SwiftSignalKitMac
|
||||||
|
#else
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public func forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
|
||||||
|
return account.postbox.modify { modifier -> Signal<Void, NoError> in
|
||||||
|
if let message = modifier.getMessage(messageId), let fromPeer = modifier.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = modifier.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) {
|
||||||
|
return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||||
|
if let updates = updates {
|
||||||
|
account.stateManager.addUpdates(updates)
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
} |> switchToLatest
|
||||||
|
}
|
@ -6,21 +6,41 @@ import Foundation
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
public class InlineBotMessageAttribute: MessageAttribute {
|
public class InlineBotMessageAttribute: MessageAttribute {
|
||||||
public let peerId: PeerId
|
public let peerId: PeerId?
|
||||||
|
public let title: String?
|
||||||
|
|
||||||
public var associatedPeerIds: [PeerId] {
|
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.peerId = peerId
|
||||||
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(decoder: PostboxDecoder) {
|
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) {
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId:
|
|||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
layer = .layer8
|
layer = .layer8
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||||
}
|
}
|
||||||
if let layer = layer {
|
if let layer = layer {
|
||||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
||||||
@ -326,6 +326,7 @@ private func pfsAcceptKey(postbox: Postbox, network: Network, peerId: PeerId, la
|
|||||||
private enum BoxedDecryptedMessage {
|
private enum BoxedDecryptedMessage {
|
||||||
case layer8(SecretApi8.DecryptedMessage)
|
case layer8(SecretApi8.DecryptedMessage)
|
||||||
case layer46(SecretApi46.DecryptedMessage)
|
case layer46(SecretApi46.DecryptedMessage)
|
||||||
|
case layer73(SecretApi73.DecryptedMessage)
|
||||||
|
|
||||||
func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) {
|
func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -350,6 +351,26 @@ private enum BoxedDecryptedMessage {
|
|||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = message.serialize(buffer, true)
|
||||||
|
case let .layer73(message):
|
||||||
|
buffer.appendInt32(0x1be31789)
|
||||||
|
let randomBytes = malloc(15)!
|
||||||
|
arc4random_buf(randomBytes, 15)
|
||||||
|
serializeBytes(Buffer(memory: randomBytes, size: 15, capacity: 15, freeWhenDone: false), buffer: buffer, boxed: false)
|
||||||
|
free(randomBytes)
|
||||||
|
buffer.appendInt32(73)
|
||||||
|
|
||||||
|
if let sequenceInfo = sequenceInfo {
|
||||||
|
let inSeqNo = sequenceInfo.topReceivedOperationIndex * 2 + (role == .creator ? 0 : 1)
|
||||||
|
let outSeqNo = sequenceInfo.operationIndex * 2 + (role == .creator ? 1 : 0)
|
||||||
|
buffer.appendInt32(inSeqNo)
|
||||||
|
buffer.appendInt32(outSeqNo)
|
||||||
|
} else {
|
||||||
|
buffer.appendInt32(0)
|
||||||
|
buffer.appendInt32(0)
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
let _ = message.serialize(buffer, true)
|
let _ = message.serialize(buffer, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,11 +471,96 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute]) -
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, layer: SecretChatLayer) -> BoxedDecryptedMessage {
|
private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute]) -> [SecretApi73.DocumentAttribute] {
|
||||||
var media: Media? = message.media.first
|
var result: [SecretApi73.DocumentAttribute] = []
|
||||||
|
for attribute in attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .FileName(fileName):
|
||||||
|
result.append(.documentAttributeFilename(fileName: fileName))
|
||||||
|
case .Animated:
|
||||||
|
result.append(.documentAttributeAnimated)
|
||||||
|
case let .Sticker(displayText, packReference, _):
|
||||||
|
var stickerSet: SecretApi73.InputStickerSet = .inputStickerSetEmpty
|
||||||
|
if let packReference = packReference, case let .name(name) = packReference {
|
||||||
|
stickerSet = .inputStickerSetShortName(shortName: name)
|
||||||
|
}
|
||||||
|
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
|
||||||
|
case let .ImageSize(size):
|
||||||
|
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
|
||||||
|
case let .Video(duration, size, videoFlags):
|
||||||
|
var flags: Int32 = 0
|
||||||
|
if videoFlags.contains(.instantRoundVideo) {
|
||||||
|
flags |= 1 << 0
|
||||||
|
}
|
||||||
|
result.append(.documentAttributeVideo(flags: flags, duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
|
||||||
|
case let .Audio(isVoice, duration, title, performer, waveform):
|
||||||
|
var flags: Int32 = 0
|
||||||
|
if isVoice {
|
||||||
|
flags |= (1 << 10)
|
||||||
|
}
|
||||||
|
if let _ = title {
|
||||||
|
flags |= Int32(1 << 0)
|
||||||
|
}
|
||||||
|
if let _ = performer {
|
||||||
|
flags |= Int32(1 << 1)
|
||||||
|
}
|
||||||
|
var waveformBuffer: Buffer?
|
||||||
|
if let waveform = waveform {
|
||||||
|
flags |= Int32(1 << 2)
|
||||||
|
waveformBuffer = Buffer(data: waveform.makeData())
|
||||||
|
}
|
||||||
|
result.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer))
|
||||||
|
case .HasLinkedStickers:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
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 messageAutoremoveTimeout: Int32 = 0
|
||||||
var replyGlobalId:Int64? = nil
|
var replyGlobalId: Int64? = nil
|
||||||
var flags:Int32 = 0
|
var flags: Int32 = 0
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
if let message = message.associatedMessages[attribute.messageId] {
|
if let message = message.associatedMessages[attribute.messageId] {
|
||||||
@ -465,30 +571,83 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viaBotName: String?
|
||||||
|
var entities: [MessageTextEntity]?
|
||||||
|
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||||
messageAutoremoveTimeout = attribute.timeout
|
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 media = media {
|
||||||
if let image = media as? TelegramMediaImage, let uploadedFile = uploadedFile, let largestRepresentation = largestImageRepresentation(image.representations) {
|
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 {
|
switch layer {
|
||||||
case .layer8:
|
case .layer8:
|
||||||
let randomBytesData = malloc(15)!
|
let randomBytesData = malloc(15)!
|
||||||
arc4random_buf(randomBytesData, 15)
|
arc4random_buf(randomBytesData, 15)
|
||||||
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
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))
|
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
|
||||||
case .layer46:
|
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)
|
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:
|
||||||
|
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: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
||||||
}
|
}
|
||||||
} else if let file = media as? TelegramMediaFile {
|
} 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 {
|
switch layer {
|
||||||
case .layer8:
|
case .layer8:
|
||||||
if let uploadedFile = uploadedFile {
|
if let uploadedFile = uploadedFile {
|
||||||
@ -496,7 +655,7 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
|||||||
arc4random_buf(randomBytesData, 15)
|
arc4random_buf(randomBytesData, 15)
|
||||||
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
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))
|
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
|
||||||
}
|
}
|
||||||
@ -517,18 +676,99 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
|||||||
if let voiceDuration = voiceDuration {
|
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))
|
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 {
|
} 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 {
|
} else {
|
||||||
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
|
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 decryptedMedia = decryptedMedia {
|
||||||
|
if let _ = viaBotName {
|
||||||
|
flags |= (1 << 11)
|
||||||
|
}
|
||||||
flags |= (1 << 9)
|
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?
|
||||||
|
|
||||||
|
if let uploadedFile = uploadedFile {
|
||||||
|
var voiceDuration: Int32?
|
||||||
|
for attribute in file.attributes {
|
||||||
|
if case let .Audio(isVoice, duration, _, _, _) = attribute {
|
||||||
|
if isVoice {
|
||||||
|
voiceDuration = Int32(duration)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 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 {
|
||||||
|
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: 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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -541,7 +781,19 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
|||||||
|
|
||||||
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty))
|
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty))
|
||||||
case .layer46:
|
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:
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,6 +809,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
|||||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
||||||
}
|
}
|
||||||
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
|
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
|
||||||
switch layer {
|
switch layer {
|
||||||
@ -568,6 +822,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
|||||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
||||||
}
|
}
|
||||||
case let .clearHistory(layer, actionGloballyUniqueId):
|
case let .clearHistory(layer, actionGloballyUniqueId):
|
||||||
switch layer {
|
switch layer {
|
||||||
@ -578,11 +834,15 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
|||||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory))
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory))
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
||||||
}
|
}
|
||||||
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
|
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
|
||||||
switch layer {
|
switch layer {
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
||||||
}
|
}
|
||||||
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
|
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
|
||||||
switch layer {
|
switch layer {
|
||||||
@ -594,31 +854,43 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
|||||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
||||||
}
|
}
|
||||||
case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, gA):
|
case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, gA):
|
||||||
switch layer {
|
switch layer {
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
||||||
}
|
}
|
||||||
case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gB, keyFingerprint):
|
case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gB, keyFingerprint):
|
||||||
switch layer {
|
switch layer {
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
||||||
}
|
}
|
||||||
case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
|
case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
|
||||||
switch layer {
|
switch layer {
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
||||||
}
|
}
|
||||||
case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
|
case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
|
||||||
switch layer {
|
switch layer {
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
||||||
}
|
}
|
||||||
case let .noop(layer, actionGloballyUniqueId):
|
case let .noop(layer, actionGloballyUniqueId):
|
||||||
switch layer {
|
switch layer {
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
||||||
}
|
}
|
||||||
case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds):
|
case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds):
|
||||||
switch layer {
|
switch layer {
|
||||||
@ -630,6 +902,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
|||||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
||||||
}
|
}
|
||||||
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
|
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
|
||||||
switch layer {
|
switch layer {
|
||||||
@ -641,6 +915,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
|||||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
||||||
case .layer46:
|
case .layer46:
|
||||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
||||||
|
case .layer73:
|
||||||
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -681,7 +957,7 @@ private func replaceOutgoingOperationWithEmptyMessage(modifier: Modifier, peerId
|
|||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
layer = .layer8
|
layer = .layer8
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let layer = layer {
|
if let layer = layer {
|
||||||
@ -699,50 +975,108 @@ 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<Void, NoError> {
|
private func sendMessage(postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal<Void, NoError> {
|
||||||
return postbox.modify { modifier -> Signal<Void, NoError> in
|
return postbox.modify { modifier -> Signal<[MediaId: Data], NoError> in
|
||||||
if let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState, let peer = modifier.getPeer(messageId.peerId) as? TelegramSecretChat {
|
if let message = modifier.getMessage(messageId) {
|
||||||
if let message = modifier.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId {
|
return messageWithThumbnailData(mediaBox: postbox.mediaBox, message: message)
|
||||||
let decryptedMessage = boxedDecryptedMessage(message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, layer: layer)
|
} else {
|
||||||
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered)
|
return .single([:])
|
||||||
|> mapToSignal { result in
|
}
|
||||||
return postbox.modify { modifier -> Void in
|
}
|
||||||
if result == nil {
|
|> switchToLatest
|
||||||
replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: globallyUniqueId)
|
|> mapToSignal { thumbnailData -> Signal<Void, NoError> in
|
||||||
} else {
|
return postbox.modify { modifier -> Signal<Void, NoError> in
|
||||||
markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
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 {
|
||||||
modifier.updateMessage(message.id, update: { currentMessage in
|
let decryptedMessage = boxedDecryptedMessage(modifier: modifier, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, thumbnailData: thumbnailData, layer: layer)
|
||||||
var flags = StoreMessageFlags(currentMessage.flags)
|
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
|
var timestamp = message.timestamp
|
||||||
if let result = result {
|
if let result = result {
|
||||||
switch result {
|
switch result {
|
||||||
case let .sentEncryptedMessage(date):
|
case let .sentEncryptedMessage(date):
|
||||||
timestamp = date
|
timestamp = date
|
||||||
case let .sentEncryptedFile(date, file):
|
case let .sentEncryptedFile(date, _):
|
||||||
timestamp = date
|
timestamp = date
|
||||||
}
|
}
|
||||||
flags.remove(.Unsent)
|
|
||||||
flags.remove(.Sending)
|
|
||||||
} else {
|
|
||||||
flags = [.Failed]
|
|
||||||
}
|
}
|
||||||
var storeForwardInfo: StoreMessageForwardInfo?
|
|
||||||
if let forwardInfo = currentMessage.forwardInfo {
|
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([messageId]), timestamp: timestamp)
|
||||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
|
||||||
}
|
modifier.updateMessage(message.id, update: { currentMessage in
|
||||||
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))
|
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 {
|
} else {
|
||||||
assertionFailure()
|
return .complete()
|
||||||
return .never()
|
|
||||||
}
|
}
|
||||||
} else {
|
} |> switchToLatest
|
||||||
return .complete()
|
}
|
||||||
}
|
|
||||||
} |> switchToLatest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
|
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
|
||||||
@ -758,6 +1092,7 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|
|||||||
markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
||||||
}
|
}
|
||||||
if let messageId = action.messageId {
|
if let messageId = action.messageId {
|
||||||
|
var resultTimestamp: Int32?
|
||||||
modifier.updateMessage(messageId, update: { currentMessage in
|
modifier.updateMessage(messageId, update: { currentMessage in
|
||||||
var flags = StoreMessageFlags(currentMessage.flags)
|
var flags = StoreMessageFlags(currentMessage.flags)
|
||||||
var timestamp = currentMessage.timestamp
|
var timestamp = currentMessage.timestamp
|
||||||
@ -773,12 +1108,17 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|
|||||||
} else {
|
} else {
|
||||||
flags = [.Failed]
|
flags = [.Failed]
|
||||||
}
|
}
|
||||||
|
resultTimestamp = timestamp
|
||||||
var storeForwardInfo: StoreMessageForwardInfo?
|
var storeForwardInfo: StoreMessageForwardInfo?
|
||||||
if let forwardInfo = currentMessage.forwardInfo {
|
if let forwardInfo = currentMessage.forwardInfo {
|
||||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
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))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -791,12 +1131,23 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|
|||||||
private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: TelegramSecretChat, state: SecretChatState, operationIndex: Int32, decryptedMessage: BoxedDecryptedMessage, globallyUniqueId: Int64, file: SecretChatOutgoingFile?, asService: Bool, wasDelivered: Bool) -> Signal<Api.messages.SentEncryptedMessage?, NoError> {
|
private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: TelegramSecretChat, state: SecretChatState, operationIndex: Int32, decryptedMessage: BoxedDecryptedMessage, globallyUniqueId: Int64, file: SecretChatOutgoingFile?, asService: Bool, wasDelivered: Bool) -> Signal<Api.messages.SentEncryptedMessage?, NoError> {
|
||||||
let payload = Buffer()
|
let payload = Buffer()
|
||||||
var sequenceInfo: SecretChatOperationSequenceInfo?
|
var sequenceInfo: SecretChatOperationSequenceInfo?
|
||||||
var maybeKey: SecretChatKey?
|
var maybeParameters: SecretChatEncryptionParameters?
|
||||||
|
|
||||||
|
let mode: SecretChatEncryptionMode
|
||||||
|
switch decryptedMessage {
|
||||||
|
case .layer8, .layer46:
|
||||||
|
mode = .v1
|
||||||
|
default:
|
||||||
|
mode = .v2(role: state.role)
|
||||||
|
}
|
||||||
|
|
||||||
switch state.embeddedState {
|
switch state.embeddedState {
|
||||||
case .terminated, .handshake:
|
case .terminated, .handshake:
|
||||||
break
|
break
|
||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
maybeKey = state.keychain.indefinitelyValidKey()
|
if let key = state.keychain.indefinitelyValidKey() {
|
||||||
|
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
|
||||||
|
}
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
let topReceivedOperationIndex: Int32
|
let topReceivedOperationIndex: Int32
|
||||||
if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex {
|
if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex {
|
||||||
@ -805,18 +1156,20 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer:
|
|||||||
topReceivedOperationIndex = -1
|
topReceivedOperationIndex = -1
|
||||||
}
|
}
|
||||||
let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex)
|
let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex)
|
||||||
maybeKey = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex)
|
if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) {
|
||||||
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeKey?.fingerprint))")
|
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)
|
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let key = maybeKey else {
|
guard let parameters = maybeParameters else {
|
||||||
Logger.shared.log("SecretChat", "no valid key found")
|
Logger.shared.log("SecretChat", "no valid key found")
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedMessage.serialize(payload, role: state.role, sequenceInfo: sequenceInfo)
|
decryptedMessage.serialize(payload, role: state.role, sequenceInfo: sequenceInfo)
|
||||||
let encryptedPayload = encryptedMessageContents(key: key, data: MemoryBuffer(payload))
|
let encryptedPayload = encryptedMessageContents(parameters: parameters, data: MemoryBuffer(payload))
|
||||||
let sendMessage: Signal<Api.messages.SentEncryptedMessage, MTRpcError>
|
let sendMessage: Signal<Api.messages.SentEncryptedMessage, MTRpcError>
|
||||||
let inputPeer = Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)
|
let inputPeer = Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ public func markMessageContentAsConsumedInteractively(postbox: Postbox, messageI
|
|||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
layer = .layer8
|
layer = .layer8
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ public struct Namespaces {
|
|||||||
public static let CloudSecretFile: Int32 = 10
|
public static let CloudSecretFile: Int32 = 10
|
||||||
public static let CloudGame: Int32 = 11
|
public static let CloudGame: Int32 = 11
|
||||||
public static let CloudInvoice: Int32 = 12
|
public static let CloudInvoice: Int32 = 12
|
||||||
|
public static let LocalWebpage: Int32 = 13
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Peer {
|
public struct Peer {
|
||||||
@ -132,6 +133,7 @@ private enum PreferencesKeyValues: Int32 {
|
|||||||
case proxySettings = 5
|
case proxySettings = 5
|
||||||
case loggingSettings = 6
|
case loggingSettings = 6
|
||||||
case coreSettings = 7
|
case coreSettings = 7
|
||||||
|
case contentPrivacySettings = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||||
@ -188,4 +190,10 @@ public struct PreferencesKeys {
|
|||||||
key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue)
|
key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue)
|
||||||
return key
|
return key
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
public static let contentPrivacySettings: ValueBoxKey = {
|
||||||
|
let key = ValueBoxKey(length: 4)
|
||||||
|
key.setInt32(0, value: PreferencesKeyValues.contentPrivacySettings.rawValue)
|
||||||
|
return key
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
@ -12,114 +12,10 @@ private func aspectFitSize(_ size: CGSize, to: CGSize) -> CGSize {
|
|||||||
return CGSize(width: floor(size.width * scale), height: floor(size.height * scale))
|
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? {
|
public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) -> EnqueueMessage? {
|
||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: results.queryId, id: result.id))
|
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 {
|
switch result.message {
|
||||||
case let .auto(caption, entities, replyMarkup):
|
case let .auto(caption, entities, replyMarkup):
|
||||||
|
@ -509,9 +509,11 @@ public final class PendingMessageManager {
|
|||||||
messages.sort { MessageIndex($0.0) < MessageIndex($1.0) }
|
messages.sort { MessageIndex($0.0) < MessageIndex($1.0) }
|
||||||
|
|
||||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
//assertionFailure()
|
for (message, content) in messages {
|
||||||
|
PendingMessageManager.sendSecretMessageContent(modifier: modifier, message: message, content: content)
|
||||||
|
}
|
||||||
|
|
||||||
return failMessages(postbox: postbox, ids: group.map { $0.0 })
|
return .complete()
|
||||||
} else if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
} else if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
var isForward = false
|
var isForward = false
|
||||||
var replyMessageId: Int32?
|
var replyMessageId: Int32?
|
||||||
@ -637,68 +639,41 @@ public final class PendingMessageManager {
|
|||||||
} |> switchToLatest
|
} |> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> {
|
private static func sendSecretMessageContent(modifier: Modifier, message: Message, content: PendingMessageUploadedContent) {
|
||||||
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in
|
var secretFile: SecretChatOutgoingFile?
|
||||||
guard let message = modifier.getMessage(messageId) else {
|
switch content {
|
||||||
return .complete()
|
case let .secretMedia(file, size, key):
|
||||||
|
if let fileReference = SecretChatOutgoingFileReference(file) {
|
||||||
|
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer: SecretChatLayer?
|
||||||
|
let state = modifier.getPeerChatState(message.id.peerId) as? SecretChatState
|
||||||
|
if let state = state {
|
||||||
|
switch state.embeddedState {
|
||||||
|
case .terminated, .handshake:
|
||||||
|
break
|
||||||
|
case .basicLayer:
|
||||||
|
layer = .layer8
|
||||||
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
|
|
||||||
var secretFile: SecretChatOutgoingFile?
|
if let state = state, let layer = layer {
|
||||||
switch content {
|
var sentAsAction = false
|
||||||
case let .secretMedia(file, size, key):
|
for media in message.media {
|
||||||
if let fileReference = SecretChatOutgoingFileReference(file) {
|
if let media = media as? TelegramMediaAction {
|
||||||
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
|
if case let .messageAutoremoveTimeoutUpdated(value) = media.action {
|
||||||
}
|
sentAsAction = true
|
||||||
default:
|
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state)
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var layer: SecretChatLayer?
|
|
||||||
let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState
|
|
||||||
if let state = state {
|
|
||||||
switch state.embeddedState {
|
|
||||||
case .terminated, .handshake:
|
|
||||||
break
|
|
||||||
case .basicLayer:
|
|
||||||
layer = .layer8
|
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
|
||||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let state = state, let layer = layer {
|
|
||||||
var sentAsAction = false
|
|
||||||
for media in message.media {
|
|
||||||
if let media = media as? TelegramMediaAction {
|
|
||||||
if case let .messageAutoremoveTimeoutUpdated(value) = media.action {
|
|
||||||
sentAsAction = true
|
|
||||||
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state)
|
|
||||||
if updatedState != state {
|
|
||||||
modifier.setPeerChatState(message.id.peerId, state: updatedState)
|
|
||||||
}
|
|
||||||
modifier.updateMessage(message.id, update: { currentMessage in
|
|
||||||
var flags = StoreMessageFlags(message.flags)
|
|
||||||
if !flags.contains(.Failed) {
|
|
||||||
flags.insert(.Sending)
|
|
||||||
}
|
|
||||||
var storeForwardInfo: StoreMessageForwardInfo?
|
|
||||||
if let forwardInfo = currentMessage.forwardInfo {
|
|
||||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
|
||||||
}
|
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sentAsAction {
|
|
||||||
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: messageId.peerId, operation: .sendMessage(layer: layer, id: messageId, file: secretFile), state: state)
|
|
||||||
if updatedState != state {
|
if updatedState != state {
|
||||||
modifier.setPeerChatState(messageId.peerId, state: updatedState)
|
modifier.setPeerChatState(message.id.peerId, state: updatedState)
|
||||||
}
|
}
|
||||||
modifier.updateMessage(messageId, update: { currentMessage in
|
modifier.updateMessage(message.id, update: { currentMessage in
|
||||||
var flags = StoreMessageFlags(message.flags)
|
var flags = StoreMessageFlags(message.flags)
|
||||||
if !flags.contains(.Failed) {
|
if !flags.contains(.Failed) {
|
||||||
flags.insert(.Sending)
|
flags.insert(.Sending)
|
||||||
@ -710,15 +685,46 @@ public final class PendingMessageManager {
|
|||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.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))
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
break
|
||||||
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, authorSignature: forwardInfo.authorSignature)
|
|
||||||
}
|
|
||||||
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], 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 !sentAsAction {
|
||||||
|
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .sendMessage(layer: layer, id: message.id, file: secretFile), state: state)
|
||||||
|
if updatedState != state {
|
||||||
|
modifier.setPeerChatState(message.id.peerId, state: updatedState)
|
||||||
|
}
|
||||||
|
modifier.updateMessage(message.id, update: { currentMessage in
|
||||||
|
var flags = StoreMessageFlags(message.flags)
|
||||||
|
if !flags.contains(.Failed) {
|
||||||
|
flags.insert(.Sending)
|
||||||
|
}
|
||||||
|
var storeForwardInfo: StoreMessageForwardInfo?
|
||||||
|
if let forwardInfo = currentMessage.forwardInfo {
|
||||||
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> {
|
||||||
|
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in
|
||||||
|
guard let message = modifier.getMessage(messageId) else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
PendingMessageManager.sendSecretMessageContent(modifier: modifier, message: message, content: content)
|
||||||
return .complete()
|
return .complete()
|
||||||
} else if let peer = modifier.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
|
} else if let peer = modifier.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
var uniqueId: Int64 = 0
|
var uniqueId: Int64 = 0
|
||||||
|
@ -40,7 +40,9 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods
|
|||||||
var autoremoveAttribute: AutoremoveTimeoutMessageAttribute?
|
var autoremoveAttribute: AutoremoveTimeoutMessageAttribute?
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
if let attribute = attribute as? OutgoingChatContextResultMessageAttribute {
|
if let attribute = attribute as? OutgoingChatContextResultMessageAttribute {
|
||||||
contextResult = attribute
|
if peerId.namespace != Namespaces.Peer.SecretChat {
|
||||||
|
contextResult = attribute
|
||||||
|
}
|
||||||
} else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
} else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||||
autoremoveAttribute = attribute
|
autoremoveAttribute = attribute
|
||||||
}
|
}
|
||||||
@ -105,7 +107,11 @@ private enum PredownloadedResource {
|
|||||||
case none
|
case none
|
||||||
}
|
}
|
||||||
|
|
||||||
private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
||||||
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
return .single(.none)
|
||||||
|
}
|
||||||
|
|
||||||
return Signal<Signal<PredownloadedResource, PendingMessageUploadError>, PendingMessageUploadError> { subscriber in
|
return Signal<Signal<PredownloadedResource, PendingMessageUploadError>, PendingMessageUploadError> { subscriber in
|
||||||
let data = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)).start(next: { data in
|
let data = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)).start(next: { data in
|
||||||
if data.complete {
|
if data.complete {
|
||||||
@ -154,7 +160,11 @@ private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaRe
|
|||||||
} |> switchToLatest
|
} |> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
||||||
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
return .single(.none)
|
||||||
|
}
|
||||||
|
|
||||||
return auxiliaryMethods.fetchResourceMediaReferenceHash(resource)
|
return auxiliaryMethods.fetchResourceMediaReferenceHash(resource)
|
||||||
|> mapToSignal { hash -> Signal<PredownloadedResource, NoError> in
|
|> mapToSignal { hash -> Signal<PredownloadedResource, NoError> in
|
||||||
if let hash = hash {
|
if let hash = hash {
|
||||||
@ -185,7 +195,7 @@ private func maybeCacheUploadedResource(postbox: Postbox, key: CachedSentMediaRe
|
|||||||
|
|
||||||
private func uploadedMediaImageContent(network: Network, postbox: Postbox, peerId: PeerId, image: TelegramMediaImage, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
private func uploadedMediaImageContent(network: Network, postbox: Postbox, peerId: PeerId, image: TelegramMediaImage, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
||||||
if let largestRepresentation = largestImageRepresentation(image.representations) {
|
if let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||||
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, resource: largestRepresentation.resource)
|
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource)
|
||||||
return predownloadedResource
|
return predownloadedResource
|
||||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||||
var referenceKey: CachedSentMediaReferenceKey?
|
var referenceKey: CachedSentMediaReferenceKey?
|
||||||
@ -324,9 +334,14 @@ private enum UploadedMediaTransform {
|
|||||||
case done(Media?)
|
case done(Media?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum UploadedMediaThumbnailResult {
|
||||||
|
case file(Api.InputFile)
|
||||||
|
case none
|
||||||
|
}
|
||||||
|
|
||||||
private enum UploadedMediaThumbnail {
|
private enum UploadedMediaThumbnail {
|
||||||
case pending
|
case pending
|
||||||
case done(Api.InputFile?)
|
case done(UploadedMediaThumbnailResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal<Api.InputFile?, PendingMessageUploadError> {
|
private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal<Api.InputFile?, PendingMessageUploadError> {
|
||||||
@ -359,7 +374,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
||||||
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, resource: file.resource) |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource) |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||||
var referenceKey: CachedSentMediaReferenceKey?
|
var referenceKey: CachedSentMediaReferenceKey?
|
||||||
switch result {
|
switch result {
|
||||||
case let .media(media):
|
case let .media(media):
|
||||||
@ -433,27 +448,40 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
|||||||
return .single(.pending)
|
return .single(.pending)
|
||||||
case let .done(media):
|
case let .done(media):
|
||||||
if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) {
|
if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) {
|
||||||
return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail)
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
return .single(.done(.none))
|
||||||
|> map { result in
|
} else {
|
||||||
return .done(result)
|
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 {
|
} else {
|
||||||
return .single(.done(nil))
|
return .single(.done(.none))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return combineLatest(upload, thumbnail)
|
return combineLatest(upload, thumbnail)
|
||||||
|> mapToSignal { content, media -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
|> mapToSignal { content, thumbnailResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||||
switch content {
|
switch content {
|
||||||
case let .progress(progress):
|
case let .progress(progress):
|
||||||
return .single(.progress(progress))
|
return .single(.progress(progress))
|
||||||
case let .inputFile(inputFile):
|
case let .inputFile(inputFile):
|
||||||
if case let .done(thumbnail) = media {
|
if case let .done(thumbnail) = thumbnailResult {
|
||||||
var flags: Int32 = 0
|
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
|
flags |= 1 << 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,10 +493,8 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//flags |= 1 << 3
|
|
||||||
|
|
||||||
if ttlSeconds != nil {
|
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
|
return postbox.modify { modifier -> Api.InputPeer? in
|
||||||
@ -477,7 +503,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
|||||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||||
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||||
if let inputPeer = inputPeer {
|
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 }
|
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||||
switch result {
|
switch result {
|
||||||
@ -498,7 +524,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
|||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
case let .inputSecretFile(file, size, key):
|
case let .inputSecretFile(file, size, key):
|
||||||
if case .done = media {
|
if case .done = thumbnailResult {
|
||||||
return .single(.content(.secretMedia(file, size, key)))
|
return .single(.content(.secretMedia(file, size, key)))
|
||||||
} else {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
|
@ -45,6 +45,10 @@ private func parsedServiceAction(_ operation: SecretChatIncomingDecryptedOperati
|
|||||||
if let parsedObject = SecretApi46.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi46.DecryptedMessage {
|
if let parsedObject = SecretApi46.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi46.DecryptedMessage {
|
||||||
return SecretChatServiceAction(apiMessage)
|
return SecretChatServiceAction(apiMessage)
|
||||||
}
|
}
|
||||||
|
case .layer73:
|
||||||
|
if let parsedObject = SecretApi73.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi73.DecryptedMessage {
|
||||||
|
return SecretChatServiceAction(apiMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -134,6 +138,18 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
|
|||||||
} else {
|
} else {
|
||||||
throw MessageParsingError.contentParsingError
|
throw MessageParsingError.contentParsingError
|
||||||
}
|
}
|
||||||
|
case .layer73:
|
||||||
|
if let parsedObject = SecretApi73.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi73.DecryptedMessage {
|
||||||
|
if let (parsedMessage, parsedResources) = parseMessage(peerId: peerId, authorId: updatedPeer.regularPeerId, tagLocalIndex: entry.tagLocalIndex, timestamp: operation.timestamp, apiMessage: apiMessage, file: operation.file, messageIdForGloballyUniqueMessageId: { id in
|
||||||
|
return modifier.messageIdForGloballyUniqueMessageId(peerId: peerId, id: id)
|
||||||
|
}) {
|
||||||
|
message = parsedMessage
|
||||||
|
resources = parsedResources
|
||||||
|
}
|
||||||
|
serviceAction = SecretChatServiceAction(apiMessage)
|
||||||
|
} else {
|
||||||
|
throw MessageParsingError.contentParsingError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch updatedState.embeddedState {
|
switch updatedState.embeddedState {
|
||||||
@ -195,10 +211,14 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
|
|||||||
case .handshake:
|
case .handshake:
|
||||||
throw MessageParsingError.invalidChatState
|
throw MessageParsingError.invalidChatState
|
||||||
case .basicLayer:
|
case .basicLayer:
|
||||||
if layerSupport >= 46 {
|
if layerSupport >= 73 {
|
||||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: 46, locallyRequestedLayer: 46, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer73, locallyRequestedLayer: 73, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
||||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer73, actionGloballyUniqueId: arc4random64(), layerSupport: 73), state: updatedState)
|
||||||
|
} else if layerSupport >= 46 {
|
||||||
|
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer46, locallyRequestedLayer: 46, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||||
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
||||||
|
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 73), state: updatedState)
|
||||||
} else {
|
} else {
|
||||||
throw MessageParsingError.contentParsingError
|
throw MessageParsingError.contentParsingError
|
||||||
}
|
}
|
||||||
@ -206,7 +226,8 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
|
|||||||
if sequenceState.layerNegotiationState.remotelyRequestedLayer != layerSupport {
|
if sequenceState.layerNegotiationState.remotelyRequestedLayer != layerSupport {
|
||||||
let updatedNegotiationState = sequenceState.layerNegotiationState.withUpdatedRemotelyRequestedLayer(layerSupport)
|
let updatedNegotiationState = sequenceState.layerNegotiationState.withUpdatedRemotelyRequestedLayer(layerSupport)
|
||||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(updatedNegotiationState)))
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(updatedNegotiationState)))
|
||||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
|
||||||
|
updatedState = secretChatCheckLayerNegotiationIfNeeded(modifier: modifier, peerId: peerId, state: updatedState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .setMessageAutoremoveTimeout(timeout):
|
case let .setMessageAutoremoveTimeout(timeout):
|
||||||
@ -378,6 +399,42 @@ extension SecretChatServiceAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SecretChatServiceAction {
|
||||||
|
init?(_ apiMessage: SecretApi73.DecryptedMessage) {
|
||||||
|
switch apiMessage {
|
||||||
|
case .decryptedMessage:
|
||||||
|
return nil
|
||||||
|
case let .decryptedMessageService(_, action):
|
||||||
|
switch action {
|
||||||
|
case let .decryptedMessageActionDeleteMessages(randomIds):
|
||||||
|
self = .deleteMessages(globallyUniqueIds: randomIds)
|
||||||
|
case .decryptedMessageActionFlushHistory:
|
||||||
|
self = .clearHistory
|
||||||
|
case let .decryptedMessageActionNotifyLayer(layer):
|
||||||
|
self = .reportLayerSupport(layer)
|
||||||
|
case let .decryptedMessageActionReadMessages(randomIds):
|
||||||
|
self = .markMessagesContentAsConsumed(globallyUniqueIds: randomIds)
|
||||||
|
case .decryptedMessageActionScreenshotMessages:
|
||||||
|
return nil
|
||||||
|
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||||
|
self = .setMessageAutoremoveTimeout(ttlSeconds)
|
||||||
|
case let .decryptedMessageActionResend(startSeqNo, endSeqNo):
|
||||||
|
self = .resendOperations(fromSeq: startSeqNo, toSeq: endSeqNo)
|
||||||
|
case let .decryptedMessageActionRequestKey(exchangeId, gA):
|
||||||
|
self = .rekeyAction(.pfsRequestKey(rekeySessionId: exchangeId, gA: MemoryBuffer(gA)))
|
||||||
|
case let .decryptedMessageActionAcceptKey(exchangeId, gB, keyFingerprint):
|
||||||
|
self = .rekeyAction(.pfsAcceptKey(rekeySessionId: exchangeId, gB: MemoryBuffer(gB), keyFingerprint: keyFingerprint))
|
||||||
|
case let .decryptedMessageActionCommitKey(exchangeId, keyFingerprint):
|
||||||
|
self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint))
|
||||||
|
case let .decryptedMessageActionAbortKey(exchangeId):
|
||||||
|
self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId))
|
||||||
|
case .decryptedMessageActionNoop:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension StoreMessage {
|
extension StoreMessage {
|
||||||
convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) {
|
convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) {
|
||||||
switch apiMessage {
|
switch apiMessage {
|
||||||
@ -431,16 +488,83 @@ extension TelegramMediaFileAttribute {
|
|||||||
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
|
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
|
||||||
case let .documentAttributeVideo(duration, w, h):
|
case let .documentAttributeVideo(duration, w, h):
|
||||||
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: [])
|
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: [])
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TelegramMediaFileAttribute {
|
||||||
|
init?(_ apiAttribute: SecretApi73.DocumentAttribute) {
|
||||||
|
switch apiAttribute {
|
||||||
|
case .documentAttributeAnimated:
|
||||||
|
self = .Animated
|
||||||
|
case let .documentAttributeAudio(flags, duration, title, performer, waveform):
|
||||||
|
let isVoice = (flags & (1 << 10)) != 0
|
||||||
|
var waveformBuffer: MemoryBuffer?
|
||||||
|
if let waveform = waveform {
|
||||||
|
let memory = malloc(waveform.size)!
|
||||||
|
memcpy(memory, waveform.data, waveform.size)
|
||||||
|
waveformBuffer = MemoryBuffer(memory: memory, capacity: waveform.size, length: waveform.size, freeWhenDone: true)
|
||||||
|
}
|
||||||
|
self = .Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: performer, waveform: waveformBuffer)
|
||||||
|
case let .documentAttributeFilename(fileName):
|
||||||
|
self = .FileName(fileName: fileName)
|
||||||
|
case let .documentAttributeImageSize(w, h):
|
||||||
|
self = .ImageSize(size: CGSize(width: CGFloat(w), height: CGFloat(h)))
|
||||||
|
case let .documentAttributeSticker(alt, stickerset):
|
||||||
|
let packReference: StickerPackReference?
|
||||||
|
switch stickerset {
|
||||||
|
case .inputStickerSetEmpty:
|
||||||
|
packReference = nil
|
||||||
|
case let .inputStickerSetShortName(shortName):
|
||||||
|
packReference = .name(shortName)
|
||||||
|
}
|
||||||
|
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
|
||||||
|
case let .documentAttributeVideo(flags, duration, w, h):
|
||||||
|
var videoFlags: TelegramMediaVideoFlags = []
|
||||||
|
if (flags & (1 << 0)) != 0 {
|
||||||
|
videoFlags.insert(.instantRoundVideo)
|
||||||
|
}
|
||||||
|
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: videoFlags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)])? {
|
private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi46.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? {
|
||||||
switch apiMessage {
|
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 text = message
|
||||||
var parsedMedia: [Media] = []
|
var parsedMedia: [Media] = []
|
||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
@ -450,6 +574,12 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
|||||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil))
|
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 {
|
if let media = media {
|
||||||
switch media {
|
switch media {
|
||||||
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption):
|
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption):
|
||||||
@ -497,7 +627,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
|||||||
text = caption
|
text = caption
|
||||||
}
|
}
|
||||||
if let file = file {
|
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] = []
|
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||||
if thumb.size != 0 {
|
if thumb.size != 0 {
|
||||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||||
@ -507,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)
|
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)
|
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] = []
|
var parsedAttributes: [TelegramMediaFileAttribute] = []
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
||||||
parsedAttributes.append(parsedAttribute)
|
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)
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -564,3 +725,214 @@ 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(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId, groupedId):
|
||||||
|
var text = message
|
||||||
|
var parsedMedia: [Media] = []
|
||||||
|
var attributes: [MessageAttribute] = []
|
||||||
|
var resources: [(MediaResource, Data)] = []
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
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):
|
||||||
|
if !caption.isEmpty {
|
||||||
|
text = caption
|
||||||
|
}
|
||||||
|
if let file = file {
|
||||||
|
var representations: [TelegramMediaImageRepresentation] = []
|
||||||
|
if thumb.size != 0 {
|
||||||
|
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||||
|
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
|
||||||
|
resources.append((resource, thumb.makeData()))
|
||||||
|
}
|
||||||
|
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size)))
|
||||||
|
let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: file.id), representations: representations, reference: nil)
|
||||||
|
parsedMedia.append(image)
|
||||||
|
}
|
||||||
|
case let .decryptedMessageMediaAudio(duration, mimeType, size, key, iv):
|
||||||
|
if let file = file {
|
||||||
|
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: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)])
|
||||||
|
parsedMedia.append(fileMedia)
|
||||||
|
}
|
||||||
|
case let .decryptedMessageMediaDocument(thumb, thumbW, thumbH, mimeType, size, key, iv, attributes, caption):
|
||||||
|
if !caption.isEmpty {
|
||||||
|
text = caption
|
||||||
|
}
|
||||||
|
if let file = file {
|
||||||
|
var parsedAttributes: [TelegramMediaFileAttribute] = []
|
||||||
|
for attribute in attributes {
|
||||||
|
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
||||||
|
parsedAttributes.append(parsedAttribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||||
|
if thumb.size != 0 {
|
||||||
|
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||||
|
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
|
||||||
|
resources.append((resource, thumb.makeData()))
|
||||||
|
}
|
||||||
|
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 .decryptedMessageMediaVideo(thumb, thumbW, thumbH, duration, mimeType, w, h, size, key, iv, caption):
|
||||||
|
if !caption.isEmpty {
|
||||||
|
text = caption
|
||||||
|
}
|
||||||
|
if let file = file {
|
||||||
|
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())
|
||||||
|
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
|
||||||
|
resources.append((resource, thumb.makeData()))
|
||||||
|
}
|
||||||
|
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):
|
||||||
|
var parsedAttributes: [TelegramMediaFileAttribute] = []
|
||||||
|
for attribute in attributes {
|
||||||
|
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupingKey: Int64?
|
||||||
|
if let groupedId = groupedId {
|
||||||
|
inner: for media in parsedMedia {
|
||||||
|
if let _ = media as? TelegramMediaImage {
|
||||||
|
groupingKey = groupedId
|
||||||
|
break inner
|
||||||
|
} else if let _ = media as? TelegramMediaFile {
|
||||||
|
groupingKey = groupedId
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) {
|
||||||
|
attributes.append(ReplyMessageAttribute(messageId: replyMessageId))
|
||||||
|
}
|
||||||
|
|
||||||
|
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||||
|
for attribute in attributes {
|
||||||
|
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||||
|
entitiesAttribute = attribute
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities)
|
||||||
|
|
||||||
|
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 .decryptedMessageActionDeleteMessages:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionFlushHistory:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionNotifyLayer:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionReadMessages:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionScreenshotMessages:
|
||||||
|
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
|
||||||
|
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||||
|
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
|
||||||
|
case .decryptedMessageActionResend:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionRequestKey:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionAcceptKey:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionCommitKey:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionAbortKey:
|
||||||
|
return nil
|
||||||
|
case .decryptedMessageActionNoop:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,114 +19,117 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
|
|||||||
modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SecretIncomingEncrypted, { entry in
|
modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SecretIncomingEncrypted, { entry in
|
||||||
if let operation = entry.contents as? SecretChatIncomingEncryptedOperation {
|
if let operation = entry.contents as? SecretChatIncomingEncryptedOperation {
|
||||||
if let key = updatedState.keychain.key(fingerprint: operation.keyFingerprint) {
|
if let key = updatedState.keychain.key(fingerprint: operation.keyFingerprint) {
|
||||||
withDecryptedMessageContents(key: key, data: operation.contents, { decryptedContents in
|
var decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v2(role: updatedState.role)), data: operation.contents)
|
||||||
if let decryptedContents = decryptedContents {
|
if decryptedContents == nil {
|
||||||
withExtendedLifetime(decryptedContents, {
|
decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v1), data: operation.contents)
|
||||||
let buffer = BufferReader(Buffer(bufferNoCopy: decryptedContents))
|
}
|
||||||
|
if let decryptedContents = decryptedContents {
|
||||||
|
withExtendedLifetime(decryptedContents, {
|
||||||
|
let buffer = BufferReader(Buffer(bufferNoCopy: decryptedContents))
|
||||||
|
|
||||||
|
do {
|
||||||
|
guard let topLevelSignature = buffer.readInt32() else {
|
||||||
|
throw MessagePreParsingError.malformedData
|
||||||
|
}
|
||||||
|
let parsedLayer: Int32
|
||||||
|
let sequenceInfo: SecretChatOperationSequenceInfo?
|
||||||
|
|
||||||
do {
|
if topLevelSignature == 0x1be31789 {
|
||||||
guard let topLevelSignature = buffer.readInt32() else {
|
guard let _ = parseBytes(buffer) else {
|
||||||
throw MessagePreParsingError.malformedData
|
|
||||||
}
|
|
||||||
let parsedLayer: Int32
|
|
||||||
let sequenceInfo: SecretChatOperationSequenceInfo?
|
|
||||||
|
|
||||||
if topLevelSignature == 0x1be31789 {
|
|
||||||
guard let _ = parseBytes(buffer) else {
|
|
||||||
throw MessagePreParsingError.malformedData
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let layerValue = buffer.readInt32() else {
|
|
||||||
throw MessagePreParsingError.malformedData
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let seqInValue = buffer.readInt32() else {
|
|
||||||
throw MessagePreParsingError.malformedData
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let seqOutValue = buffer.readInt32() else {
|
|
||||||
throw MessagePreParsingError.malformedData
|
|
||||||
}
|
|
||||||
|
|
||||||
switch updatedState.role {
|
|
||||||
case .creator:
|
|
||||||
if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) == 0) || (seqOutValue & 1) != 0 {
|
|
||||||
throw MessagePreParsingError.protocolViolation
|
|
||||||
}
|
|
||||||
case .participant:
|
|
||||||
if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) != 0) || (seqOutValue & 1) == 0 {
|
|
||||||
throw MessagePreParsingError.protocolViolation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: seqInValue / 2, operationIndex: seqOutValue / 2)
|
|
||||||
|
|
||||||
if layerValue == 17 {
|
|
||||||
parsedLayer = 46
|
|
||||||
} else {
|
|
||||||
parsedLayer = layerValue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parsedLayer = 8
|
|
||||||
sequenceInfo = nil
|
|
||||||
buffer.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let messageContents = buffer.readBuffer(decryptedContents.length - Int(buffer.offset)) else {
|
|
||||||
throw MessagePreParsingError.malformedData
|
throw MessagePreParsingError.malformedData
|
||||||
}
|
}
|
||||||
|
|
||||||
let entryTagLocalIndex: StorePeerOperationLogEntryTagLocalIndex
|
guard let layerValue = buffer.readInt32() else {
|
||||||
|
throw MessagePreParsingError.malformedData
|
||||||
|
}
|
||||||
|
|
||||||
switch updatedState.embeddedState {
|
guard let seqInValue = buffer.readInt32() else {
|
||||||
case .terminated:
|
throw MessagePreParsingError.malformedData
|
||||||
throw MessagePreParsingError.invalidChatState
|
}
|
||||||
case .handshake:
|
|
||||||
throw MessagePreParsingError.invalidChatState
|
guard let seqOutValue = buffer.readInt32() else {
|
||||||
case .basicLayer:
|
throw MessagePreParsingError.malformedData
|
||||||
if parsedLayer >= 46 {
|
}
|
||||||
guard let sequenceInfo = sequenceInfo else {
|
|
||||||
throw MessagePreParsingError.protocolViolation
|
switch updatedState.role {
|
||||||
}
|
case .creator:
|
||||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: parsedLayer, locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) == 0) || (seqOutValue & 1) != 0 {
|
||||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
throw MessagePreParsingError.protocolViolation
|
||||||
modifier.setPeerChatState(peerId, state: updatedState)
|
}
|
||||||
entryTagLocalIndex = .manual(sequenceBasedLayerState.baseIncomingOperationIndex + sequenceInfo.operationIndex)
|
case .participant:
|
||||||
} else {
|
if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) != 0) || (seqOutValue & 1) == 0 {
|
||||||
if parsedLayer != 8 && parsedLayer != 17 {
|
|
||||||
throw MessagePreParsingError.protocolViolation
|
|
||||||
}
|
|
||||||
entryTagLocalIndex = .automatic
|
|
||||||
}
|
|
||||||
case let .sequenceBasedLayer(sequenceState):
|
|
||||||
if parsedLayer < 46 {
|
|
||||||
throw MessagePreParsingError.protocolViolation
|
throw MessagePreParsingError.protocolViolation
|
||||||
}
|
}
|
||||||
|
|
||||||
entryTagLocalIndex = .manual(sequenceState.baseIncomingOperationIndex + sequenceInfo!.operationIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretIncomingDecrypted, tagLocalIndex: entryTagLocalIndex, tagMergedIndex: .none, contents: SecretChatIncomingDecryptedOperation(timestamp: operation.timestamp, layer: parsedLayer, sequenceInfo: sequenceInfo, contents: MemoryBuffer(messageContents), file: operation.mediaFileReference))
|
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: seqInValue / 2, operationIndex: seqOutValue / 2)
|
||||||
addedDecryptedOperations = true
|
|
||||||
} catch let error {
|
if layerValue == 17 {
|
||||||
if let error = error as? MessagePreParsingError {
|
parsedLayer = 46
|
||||||
switch error {
|
} else {
|
||||||
case .invalidChatState:
|
parsedLayer = layerValue
|
||||||
break
|
|
||||||
case .malformedData, .protocolViolation:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Logger.shared.log("SecretChat", "peerId \(peerId) malformed data after decryption")
|
} else {
|
||||||
|
parsedLayer = 8
|
||||||
|
sequenceInfo = nil
|
||||||
|
buffer.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTagLocalIndices.append(entry.tagLocalIndex)
|
guard let messageContents = buffer.readBuffer(decryptedContents.length - Int(buffer.offset)) else {
|
||||||
})
|
throw MessagePreParsingError.malformedData
|
||||||
} else {
|
}
|
||||||
Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content")
|
|
||||||
|
let entryTagLocalIndex: StorePeerOperationLogEntryTagLocalIndex
|
||||||
|
|
||||||
|
switch updatedState.embeddedState {
|
||||||
|
case .terminated:
|
||||||
|
throw MessagePreParsingError.invalidChatState
|
||||||
|
case .handshake:
|
||||||
|
throw MessagePreParsingError.invalidChatState
|
||||||
|
case .basicLayer:
|
||||||
|
if parsedLayer >= 46 {
|
||||||
|
guard let sequenceInfo = sequenceInfo else {
|
||||||
|
throw MessagePreParsingError.protocolViolation
|
||||||
|
}
|
||||||
|
|
||||||
|
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: secretChatCommonSupportedLayer(remoteLayer: parsedLayer), locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||||
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
||||||
|
modifier.setPeerChatState(peerId, state: updatedState)
|
||||||
|
entryTagLocalIndex = .manual(sequenceBasedLayerState.baseIncomingOperationIndex + sequenceInfo.operationIndex)
|
||||||
|
} else {
|
||||||
|
if parsedLayer != 8 && parsedLayer != 17 {
|
||||||
|
throw MessagePreParsingError.protocolViolation
|
||||||
|
}
|
||||||
|
entryTagLocalIndex = .automatic
|
||||||
|
}
|
||||||
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
|
if parsedLayer < 46 {
|
||||||
|
throw MessagePreParsingError.protocolViolation
|
||||||
|
}
|
||||||
|
|
||||||
|
entryTagLocalIndex = .manual(sequenceState.baseIncomingOperationIndex + sequenceInfo!.operationIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretIncomingDecrypted, tagLocalIndex: entryTagLocalIndex, tagMergedIndex: .none, contents: SecretChatIncomingDecryptedOperation(timestamp: operation.timestamp, layer: parsedLayer, sequenceInfo: sequenceInfo, contents: MemoryBuffer(messageContents), file: operation.mediaFileReference))
|
||||||
|
addedDecryptedOperations = true
|
||||||
|
} catch let error {
|
||||||
|
if let error = error as? MessagePreParsingError {
|
||||||
|
switch error {
|
||||||
|
case .invalidChatState:
|
||||||
|
break
|
||||||
|
case .malformedData, .protocolViolation:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.shared.log("SecretChat", "peerId \(peerId) malformed data after decryption")
|
||||||
|
}
|
||||||
|
|
||||||
removeTagLocalIndices.append(entry.tagLocalIndex)
|
removeTagLocalIndices.append(entry.tagLocalIndex)
|
||||||
}
|
})
|
||||||
})
|
} else {
|
||||||
|
Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content")
|
||||||
|
removeTagLocalIndices.append(entry.tagLocalIndex)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("SecretChat", "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist")
|
Logger.shared.log("SecretChat", "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist")
|
||||||
}
|
}
|
||||||
|
1524
TelegramCore/SecretApiLayer73.swift
Normal file
1524
TelegramCore/SecretApiLayer73.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,121 +7,224 @@ import Foundation
|
|||||||
import MtProtoKitDynamic
|
import MtProtoKitDynamic
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer) -> (aesKey: Data, aesIv: Data) {
|
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer, mode: SecretChatEncryptionMode) -> (aesKey: Data, aesIv: Data) {
|
||||||
let x: Int = 0
|
switch mode {
|
||||||
|
case .v1:
|
||||||
var sha1AData = Data()
|
let x: Int = 0
|
||||||
sha1AData.count = 16 + 32
|
|
||||||
sha1AData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
var sha1AData = Data()
|
||||||
memcpy(bytes, msgKey, 16)
|
sha1AData.count = 16 + 32
|
||||||
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: x), 32)
|
sha1AData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes, msgKey, 16)
|
||||||
|
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: x), 32)
|
||||||
|
}
|
||||||
|
let sha1A = MTSha1(sha1AData)!
|
||||||
|
|
||||||
|
var sha1BData = Data()
|
||||||
|
sha1BData.count = 16 + 16 + 16
|
||||||
|
sha1BData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes, key.key.memory.advanced(by: 32 + x), 16)
|
||||||
|
memcpy(bytes.advanced(by: 16), msgKey, 16)
|
||||||
|
memcpy(bytes.advanced(by: 16 + 16), key.key.memory.advanced(by: 48 + x), 16)
|
||||||
|
}
|
||||||
|
let sha1B = MTSha1(sha1BData)!
|
||||||
|
|
||||||
|
var sha1CData = Data()
|
||||||
|
sha1CData.count = 32 + 16
|
||||||
|
sha1CData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes, key.key.memory.advanced(by: 64 + x), 32)
|
||||||
|
memcpy(bytes.advanced(by: 32), msgKey, 16)
|
||||||
|
}
|
||||||
|
let sha1C = MTSha1(sha1CData)!
|
||||||
|
|
||||||
|
var sha1DData = Data()
|
||||||
|
sha1DData.count = 16 + 32
|
||||||
|
sha1DData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes, msgKey, 16)
|
||||||
|
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: 96 + x), 32)
|
||||||
|
}
|
||||||
|
let sha1D = MTSha1(sha1DData)!
|
||||||
|
|
||||||
|
var aesKey = Data()
|
||||||
|
aesKey.count = 8 + 12 + 12
|
||||||
|
aesKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes, sha1A, 8)
|
||||||
|
}
|
||||||
|
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes.advanced(by: 8), sha1B.advanced(by: 8), 12)
|
||||||
|
}
|
||||||
|
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes.advanced(by: 8 + 12), sha1C.advanced(by: 4), 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var aesIv = Data()
|
||||||
|
aesIv.count = 12 + 8 + 4 + 8
|
||||||
|
aesIv.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes, sha1A.advanced(by: 8), 12)
|
||||||
|
}
|
||||||
|
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes.advanced(by: 12), sha1B, 8)
|
||||||
|
}
|
||||||
|
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes.advanced(by: 12 + 8), sha1C.advanced(by: 16), 4)
|
||||||
|
}
|
||||||
|
sha1D.withUnsafeBytes { (sha1D: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(bytes.advanced(by: 12 + 8 + 4), sha1D, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (aesKey, aesIv)
|
||||||
|
case let .v2(role):
|
||||||
|
var xValue: Int
|
||||||
|
switch role {
|
||||||
|
case .creator:
|
||||||
|
xValue = 0
|
||||||
|
case .participant:
|
||||||
|
xValue = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
var sha256_a_data = Data()
|
||||||
|
sha256_a_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||||
|
sha256_a_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: xValue), count: 36)
|
||||||
|
|
||||||
|
let sha256_a = MTSha256(sha256_a_data)!
|
||||||
|
|
||||||
|
var sha256_b_data = Data()
|
||||||
|
sha256_b_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 40 + xValue), count: 36)
|
||||||
|
sha256_b_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||||
|
|
||||||
|
let sha256_b = MTSha256(sha256_b_data)!
|
||||||
|
|
||||||
|
var aesKey = Data()
|
||||||
|
aesKey.append(sha256_a.subdata(in: 0 ..< (0 + 8)))
|
||||||
|
aesKey.append(sha256_b.subdata(in: 8 ..< (8 + 16)))
|
||||||
|
aesKey.append(sha256_a.subdata(in: 24 ..< (24 + 8)))
|
||||||
|
|
||||||
|
var aesIv = Data()
|
||||||
|
aesIv.append(sha256_b.subdata(in: 0 ..< (0 + 8)))
|
||||||
|
aesIv.append(sha256_a.subdata(in: 8 ..< (8 + 16)))
|
||||||
|
aesIv.append(sha256_b.subdata(in: 24 ..< (24 + 8)))
|
||||||
|
|
||||||
|
return (aesKey, aesIv)
|
||||||
}
|
}
|
||||||
let sha1A = MTSha1(sha1AData)!
|
|
||||||
|
|
||||||
var sha1BData = Data()
|
|
||||||
sha1BData.count = 16 + 16 + 16
|
|
||||||
sha1BData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes, key.key.memory.advanced(by: 32 + x), 16)
|
|
||||||
memcpy(bytes.advanced(by: 16), msgKey, 16)
|
|
||||||
memcpy(bytes.advanced(by: 16 + 16), key.key.memory.advanced(by: 48 + x), 16)
|
|
||||||
}
|
|
||||||
let sha1B = MTSha1(sha1BData)!
|
|
||||||
|
|
||||||
var sha1CData = Data()
|
|
||||||
sha1CData.count = 32 + 16
|
|
||||||
sha1CData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes, key.key.memory.advanced(by: 64 + x), 32)
|
|
||||||
memcpy(bytes.advanced(by: 32), msgKey, 16)
|
|
||||||
}
|
|
||||||
let sha1C = MTSha1(sha1CData)!
|
|
||||||
|
|
||||||
var sha1DData = Data()
|
|
||||||
sha1DData.count = 16 + 32
|
|
||||||
sha1DData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes, msgKey, 16)
|
|
||||||
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: 96 + x), 32)
|
|
||||||
}
|
|
||||||
let sha1D = MTSha1(sha1DData)!
|
|
||||||
|
|
||||||
var aesKey = Data()
|
|
||||||
aesKey.count = 8 + 12 + 12
|
|
||||||
aesKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
||||||
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes, sha1A, 8)
|
|
||||||
}
|
|
||||||
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes.advanced(by: 8), sha1B.advanced(by: 8), 12)
|
|
||||||
}
|
|
||||||
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes.advanced(by: 8 + 12), sha1C.advanced(by: 4), 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var aesIv = Data()
|
|
||||||
aesIv.count = 12 + 8 + 4 + 8
|
|
||||||
aesIv.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
||||||
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes, sha1A.advanced(by: 8), 12)
|
|
||||||
}
|
|
||||||
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes.advanced(by: 12), sha1B, 8)
|
|
||||||
}
|
|
||||||
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes.advanced(by: 12 + 8), sha1C.advanced(by: 16), 4)
|
|
||||||
}
|
|
||||||
sha1D.withUnsafeBytes { (sha1D: UnsafePointer<UInt8>) -> Void in
|
|
||||||
memcpy(bytes.advanced(by: 12 + 8 + 4), sha1D, 8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (aesKey, aesIv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (MemoryBuffer?) -> Void) {
|
func withDecryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> MemoryBuffer? {
|
||||||
assert(key.key.length == 256)
|
assert(parameters.key.key.length == 256)
|
||||||
|
|
||||||
if data.length < 4 + 16 + 16 {
|
if data.length < 4 + 16 + 16 {
|
||||||
f(nil)
|
return nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let msgKey = data.memory.advanced(by: 8)
|
let msgKey = data.memory.advanced(by: 8)
|
||||||
|
|
||||||
let (aesKey, aesIv) = messageKey(key: key, msgKey: msgKey)
|
switch parameters.mode {
|
||||||
|
case .v1:
|
||||||
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: parameters.mode)
|
||||||
|
|
||||||
if decryptedData.count < 4 * 3 {
|
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
||||||
f(nil)
|
|
||||||
return
|
if decryptedData.count < 4 * 3 {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
var payloadLength: Int32 = 0
|
|
||||||
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
var payloadLength: Int32 = 0
|
||||||
memcpy(&payloadLength, bytes, 4)
|
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||||
}
|
memcpy(&payloadLength, bytes, 4)
|
||||||
|
}
|
||||||
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
|
||||||
if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 {
|
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
||||||
f(nil)
|
if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)!
|
let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)!
|
||||||
let msgKeyMatches = calculatedMsgKeyData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Bool in
|
let msgKeyMatches = calculatedMsgKeyData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Bool in
|
||||||
return memcmp(bytes.advanced(by: calculatedMsgKeyData.count - 16), msgKey, 16) == 0
|
return memcmp(bytes.advanced(by: calculatedMsgKeyData.count - 16), msgKey, 16) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if !msgKeyMatches {
|
if !msgKeyMatches {
|
||||||
f(nil)
|
return nil
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||||
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
|
||||||
f(MemoryBuffer(memory: UnsafeMutablePointer(mutating: bytes.advanced(by: 4)), capacity: Int(payloadLength), length: Int(payloadLength), freeWhenDone: false))
|
}
|
||||||
|
return MemoryBuffer(data: result)
|
||||||
|
case let .v2(role):
|
||||||
|
let senderRole: SecretChatRole
|
||||||
|
switch role {
|
||||||
|
case .creator:
|
||||||
|
senderRole = .participant
|
||||||
|
case .participant:
|
||||||
|
senderRole = .creator
|
||||||
|
}
|
||||||
|
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: .v2(role: senderRole))
|
||||||
|
|
||||||
|
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
||||||
|
|
||||||
|
if decryptedData.count < 4 * 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadLength: Int32 = 0
|
||||||
|
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||||
|
memcpy(&payloadLength, bytes, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
||||||
|
|
||||||
|
let xValue: Int
|
||||||
|
switch role {
|
||||||
|
case .creator:
|
||||||
|
xValue = 8
|
||||||
|
case .participant:
|
||||||
|
xValue = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyLargeData = Data()
|
||||||
|
keyLargeData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
|
||||||
|
keyLargeData.append(decryptedData)
|
||||||
|
|
||||||
|
let keyLarge = MTSha256(keyLargeData)!
|
||||||
|
let localMessageKey = keyLarge.subdata(in: 8 ..< (8 + 16))
|
||||||
|
|
||||||
|
let msgKeyData = Data(bytes: msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||||
|
|
||||||
|
if Int(payloadLength) <= 0 || Int(payloadLength) > decryptedData.count - 4 || paddingLength < 12 || paddingLength > 1024 {
|
||||||
|
|
||||||
|
if localMessageKey != msgKeyData {
|
||||||
|
Logger.shared.log("SecretChatEncryption", "message key doesn't match (length check)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if localMessageKey != msgKeyData {
|
||||||
|
Logger.shared.log("SecretChatEncryption", "message key doesn't match")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||||
|
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
|
||||||
|
}
|
||||||
|
return MemoryBuffer(data: result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
|
enum SecretChatEncryptionMode {
|
||||||
|
case v1
|
||||||
|
case v2(role: SecretChatRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SecretChatEncryptionParameters {
|
||||||
|
let key: SecretChatKey
|
||||||
|
let mode: SecretChatEncryptionMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> Data {
|
||||||
var payloadLength: Int32 = Int32(data.length)
|
var payloadLength: Int32 = Int32(data.length)
|
||||||
var payloadData = Data()
|
var payloadData = Data()
|
||||||
withUnsafeBytes(of: &payloadLength, { bytes -> Void in
|
withUnsafeBytes(of: &payloadLength, { bytes -> Void in
|
||||||
@ -129,33 +232,94 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
|
|||||||
})
|
})
|
||||||
payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length)
|
payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length)
|
||||||
|
|
||||||
var msgKey = MTSha1(payloadData)!
|
switch parameters.mode {
|
||||||
msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data())
|
case .v1:
|
||||||
|
var msgKey = MTSha1(payloadData)!
|
||||||
var randomBuf = malloc(16)!
|
msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data())
|
||||||
defer {
|
|
||||||
free(randomBuf)
|
var randomBuf = malloc(16)!
|
||||||
|
defer {
|
||||||
|
free(randomBuf)
|
||||||
|
}
|
||||||
|
let randomBytes = randomBuf.assumingMemoryBound(to: UInt8.self)
|
||||||
|
arc4random_buf(randomBuf, 16)
|
||||||
|
|
||||||
|
var randomIndex = 0
|
||||||
|
while payloadData.count % 16 != 0 {
|
||||||
|
payloadData.append(randomBytes.advanced(by: randomIndex), count: 1)
|
||||||
|
randomIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
||||||
|
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedData = MTAesEncrypt(payloadData, aesKey, aesIv)!
|
||||||
|
var encryptedPayload = Data()
|
||||||
|
var keyFingerprint: Int64 = parameters.key.fingerprint
|
||||||
|
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
||||||
|
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
||||||
|
})
|
||||||
|
encryptedPayload.append(msgKey)
|
||||||
|
encryptedPayload.append(encryptedData)
|
||||||
|
return encryptedPayload
|
||||||
|
case let .v2(role):
|
||||||
|
var randomBytes = Data(count: 128)
|
||||||
|
randomBytes.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
|
||||||
|
arc4random_buf(bytes, randomBytes.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
var decryptedData = payloadData
|
||||||
|
var take = 0
|
||||||
|
while take < 12 {
|
||||||
|
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||||
|
take += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while decryptedData.count % 16 != 0 {
|
||||||
|
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||||
|
take += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var remainingCount = Int(arc4random_uniform(UInt32(72 + 1 - take)))
|
||||||
|
while remainingCount % 16 != 0 {
|
||||||
|
remainingCount -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0 ..< remainingCount {
|
||||||
|
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||||
|
take += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var xValue: Int
|
||||||
|
switch role {
|
||||||
|
case .creator:
|
||||||
|
xValue = 0
|
||||||
|
case .participant:
|
||||||
|
xValue = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyData = Data()
|
||||||
|
keyData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
|
||||||
|
|
||||||
|
keyData.append(decryptedData)
|
||||||
|
|
||||||
|
let keyLarge = MTSha256(keyData)!
|
||||||
|
|
||||||
|
let msgKey = keyLarge.subdata(in: 8 ..< (8 + 16))
|
||||||
|
|
||||||
|
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
||||||
|
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedData = MTAesEncrypt(decryptedData, aesKey, aesIv)!
|
||||||
|
var encryptedPayload = Data()
|
||||||
|
var keyFingerprint: Int64 = parameters.key.fingerprint
|
||||||
|
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
||||||
|
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
||||||
|
})
|
||||||
|
encryptedPayload.append(msgKey)
|
||||||
|
encryptedPayload.append(encryptedData)
|
||||||
|
return encryptedPayload
|
||||||
}
|
}
|
||||||
let randomBytes = randomBuf.assumingMemoryBound(to: UInt8.self)
|
|
||||||
arc4random_buf(randomBuf, 16)
|
|
||||||
|
|
||||||
var randomIndex = 0
|
|
||||||
while payloadData.count % 16 != 0 {
|
|
||||||
payloadData.append(randomBytes.advanced(by: randomIndex), count: 1)
|
|
||||||
randomIndex += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
|
||||||
return messageKey(key: key, msgKey: bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
let encryptedData = MTAesEncrypt(payloadData, aesKey, aesIv)!
|
|
||||||
var encryptedPayload = Data()
|
|
||||||
var keyFingerprint: Int64 = key.fingerprint
|
|
||||||
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
|
||||||
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
|
||||||
})
|
|
||||||
encryptedPayload.append(msgKey)
|
|
||||||
encryptedPayload.append(encryptedData)
|
|
||||||
return encryptedPayload
|
|
||||||
}
|
}
|
||||||
|
63
TelegramCore/SecretChatLayerNegotiation.swift
Normal file
63
TelegramCore/SecretChatLayerNegotiation.swift
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import Foundation
|
||||||
|
#if os(macOS)
|
||||||
|
import PostboxMac
|
||||||
|
import SwiftSignalKitMac
|
||||||
|
#else
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private let topSupportedLayer: SecretChatSequenceBasedLayer = .layer73
|
||||||
|
|
||||||
|
func secretChatCommonSupportedLayer(remoteLayer: Int32) -> SecretChatSequenceBasedLayer {
|
||||||
|
switch remoteLayer {
|
||||||
|
case 46:
|
||||||
|
return .layer46
|
||||||
|
case 73:
|
||||||
|
return .layer73
|
||||||
|
default:
|
||||||
|
return topSupportedLayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: Modifier, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||||
|
switch state.embeddedState {
|
||||||
|
case .basicLayer:
|
||||||
|
var updatedState = state
|
||||||
|
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
|
||||||
|
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: sequenceState.layerNegotiationState.activeLayer.secretChatLayer, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
|
||||||
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedLocallyRequestedLayer(topSupportedLayer.rawValue))))
|
||||||
|
return updatedState
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func secretChatCheckLayerNegotiationIfNeeded(modifier: Modifier, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||||
|
switch state.embeddedState {
|
||||||
|
case let .sequenceBasedLayer(sequenceState):
|
||||||
|
if sequenceState.layerNegotiationState.activeLayer != topSupportedLayer {
|
||||||
|
var updatedState = state
|
||||||
|
|
||||||
|
if let remotelyRequestedLayer = sequenceState.layerNegotiationState.remotelyRequestedLayer {
|
||||||
|
let updatedSequenceState = sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedActiveLayer(secretChatCommonSupportedLayer(remoteLayer: remotelyRequestedLayer)))
|
||||||
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(updatedSequenceState))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequenceState.layerNegotiationState.locallyRequestedLayer ?? 0) < topSupportedLayer.rawValue {
|
||||||
|
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: peerId, state: updatedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedState
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
case .basicLayer:
|
||||||
|
return state
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
@ -78,6 +78,16 @@ struct SecretChatOutgoingFile: PostboxCoding {
|
|||||||
|
|
||||||
enum SecretChatSequenceBasedLayer: Int32 {
|
enum SecretChatSequenceBasedLayer: Int32 {
|
||||||
case layer46 = 46
|
case layer46 = 46
|
||||||
|
case layer73 = 73
|
||||||
|
|
||||||
|
var secretChatLayer: SecretChatLayer {
|
||||||
|
switch self {
|
||||||
|
case .layer46:
|
||||||
|
return .layer46
|
||||||
|
case .layer73:
|
||||||
|
return .layer73
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SecretChatOutgoingOperationValue: Int32 {
|
private enum SecretChatOutgoingOperationValue: Int32 {
|
||||||
|
@ -23,7 +23,7 @@ func secretChatInitiateRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId,
|
|||||||
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||||
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
|
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||||
|
|
||||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
|
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
|
||||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: sessionId, data: .requesting))))
|
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: sessionId, data: .requesting))))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -69,7 +69,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
|||||||
|
|
||||||
assert(remoteKeyFingerprint == keyFingerprint)
|
assert(remoteKeyFingerprint == keyFingerprint)
|
||||||
|
|
||||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
|
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
|
||||||
|
|
||||||
let keyValidityOperationIndex = modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
let keyValidityOperationIndex = modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
||||||
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex)
|
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex)
|
||||||
@ -92,7 +92,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
|||||||
return SecretChatKey(fingerprint: keyFingerprint, key: key, validity: .sequenceBasedIndexRange(fromCanonicalIndex: keyValidityOperationCanonicalIndex), useCount: 0)
|
return SecretChatKey(fingerprint: keyFingerprint, key: key, validity: .sequenceBasedIndexRange(fromCanonicalIndex: keyValidityOperationCanonicalIndex), useCount: 0)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: .layer46, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
|
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
|
||||||
|
|
||||||
return updatedState
|
return updatedState
|
||||||
} else {
|
} else {
|
||||||
@ -107,7 +107,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
|||||||
switch rekeySession.data {
|
switch rekeySession.data {
|
||||||
case .requesting, .requested:
|
case .requesting, .requested:
|
||||||
if rekeySessionId < rekeySession.id {
|
if rekeySessionId < rekeySession.id {
|
||||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
|
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
|
||||||
} else {
|
} else {
|
||||||
acceptSession = false
|
acceptSession = false
|
||||||
}
|
}
|
||||||
@ -117,14 +117,13 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if acceptSession {
|
if acceptSession {
|
||||||
let sessionId = arc4random64()
|
|
||||||
let bBytes = malloc(256)!
|
let bBytes = malloc(256)!
|
||||||
let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
|
let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
|
||||||
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
|
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||||
|
|
||||||
let rekeySession = SecretChatRekeySessionState(id: rekeySessionId, data: .accepting)
|
let rekeySession = SecretChatRekeySessionState(id: rekeySessionId, data: .accepting)
|
||||||
|
|
||||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
|
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
|
||||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(rekeySession)))
|
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(rekeySession)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ public enum SecretChatRole: Int32 {
|
|||||||
enum SecretChatLayer: Int32 {
|
enum SecretChatLayer: Int32 {
|
||||||
case layer8 = 8
|
case layer8 = 8
|
||||||
case layer46 = 46
|
case layer46 = 46
|
||||||
|
case layer73 = 73
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SecretChatKeySha1Fingerprint: PostboxCoding, Equatable {
|
public struct SecretChatKeySha1Fingerprint: PostboxCoding, Equatable {
|
||||||
@ -240,24 +241,24 @@ enum SecretChatHandshakeState: PostboxCoding, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
|
struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
|
||||||
let activeLayer: Int32
|
let activeLayer: SecretChatSequenceBasedLayer
|
||||||
let locallyRequestedLayer: Int32?
|
let locallyRequestedLayer: Int32?
|
||||||
let remotelyRequestedLayer: Int32?
|
let remotelyRequestedLayer: Int32?
|
||||||
|
|
||||||
init(activeLayer: Int32, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
|
init(activeLayer: SecretChatSequenceBasedLayer, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
|
||||||
self.activeLayer = activeLayer
|
self.activeLayer = activeLayer
|
||||||
self.locallyRequestedLayer = locallyRequestedLayer
|
self.locallyRequestedLayer = locallyRequestedLayer
|
||||||
self.remotelyRequestedLayer = remotelyRequestedLayer
|
self.remotelyRequestedLayer = remotelyRequestedLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
init(decoder: PostboxDecoder) {
|
init(decoder: PostboxDecoder) {
|
||||||
self.activeLayer = decoder.decodeInt32ForKey("a", orElse: 0)
|
self.activeLayer = SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("a", orElse: 0)) ?? .layer46
|
||||||
self.locallyRequestedLayer = decoder.decodeOptionalInt32ForKey("lr")
|
self.locallyRequestedLayer = decoder.decodeOptionalInt32ForKey("lr")
|
||||||
self.remotelyRequestedLayer = decoder.decodeOptionalInt32ForKey("rr")
|
self.remotelyRequestedLayer = decoder.decodeOptionalInt32ForKey("rr")
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(_ encoder: PostboxEncoder) {
|
func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeInt32(self.activeLayer, forKey: "a")
|
encoder.encodeInt32(self.activeLayer.rawValue, forKey: "a")
|
||||||
if let locallyRequestedLayer = self.locallyRequestedLayer {
|
if let locallyRequestedLayer = self.locallyRequestedLayer {
|
||||||
encoder.encodeInt32(locallyRequestedLayer, forKey: "lr")
|
encoder.encodeInt32(locallyRequestedLayer, forKey: "lr")
|
||||||
} else {
|
} else {
|
||||||
@ -283,6 +284,14 @@ struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withUpdatedActiveLayer(_ activeLayer: SecretChatSequenceBasedLayer) -> SecretChatLayerNegotiationState {
|
||||||
|
return SecretChatLayerNegotiationState(activeLayer: activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: self.remotelyRequestedLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedLocallyRequestedLayer(_ locallyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
|
||||||
|
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: locallyRequestedLayer, remotelyRequestedLayer: self.remotelyRequestedLayer)
|
||||||
|
}
|
||||||
|
|
||||||
func withUpdatedRemotelyRequestedLayer(_ remotelyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
|
func withUpdatedRemotelyRequestedLayer(_ remotelyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
|
||||||
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: remotelyRequestedLayer)
|
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: remotelyRequestedLayer)
|
||||||
}
|
}
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
#if os(macOS)
|
|
||||||
import PostboxMac
|
|
||||||
#else
|
|
||||||
import Postbox
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public enum SecretChatBridgeRole: Int32 {
|
|
||||||
case creator
|
|
||||||
case participant
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct SecretChatStateBridge {
|
|
||||||
public let role:SecretChatBridgeRole
|
|
||||||
public init(role: SecretChatBridgeRole) {
|
|
||||||
self.role = role
|
|
||||||
}
|
|
||||||
|
|
||||||
public var state: PeerChatState {
|
|
||||||
return SecretChatState(role: SecretChatRole(rawValue: role.rawValue)!, embeddedState: .terminated, keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public func terminateLegacySecretChat(modifier: Modifier, peerId: PeerId, state: SecretChatStateBridge) -> PeerChatState {
|
|
||||||
return addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: false), state: state.state as! SecretChatState).withUpdatedEmbeddedState(.terminated)
|
|
||||||
}
|
|
@ -90,16 +90,11 @@ public class BoxedMessage: NSObject {
|
|||||||
#else
|
#else
|
||||||
redact = true
|
redact = true
|
||||||
#endif
|
#endif
|
||||||
return recursiveDescription(redact: redact, of: self.body)
|
if let body = self.body as? RedactingCustomStringConvertible {
|
||||||
/*if let body = self.body as? RedactingCustomStringConvertible {
|
return body.redactingDescription(redact)
|
||||||
|
|
||||||
return body.redactingDescription(false)
|
|
||||||
#else
|
|
||||||
return body.redactingDescription(true)
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
return "\(self.body)"
|
return recursiveDescription(redact: redact, of: self.body)
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
return (TelegramMediaExpiredContent(data: .file), nil)
|
return (TelegramMediaExpiredContent(data: .file), nil)
|
||||||
}
|
}
|
||||||
case let .messageMediaWebPage(webpage):
|
case let .messageMediaWebPage(webpage):
|
||||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) {
|
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
||||||
return (mediaWebpage, nil)
|
return (mediaWebpage, nil)
|
||||||
}
|
}
|
||||||
case .messageMediaUnsupported:
|
case .messageMediaUnsupported:
|
||||||
@ -475,7 +475,7 @@ extension StoreMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let viaBotId = viaBotId {
|
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 {
|
if let replyToMsgId = replyToMsgId {
|
||||||
|
@ -191,7 +191,7 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum TelegramMediaWebpageContent {
|
public enum TelegramMediaWebpageContent {
|
||||||
case Pending(Int32)
|
case Pending(Int32, String?)
|
||||||
case Loaded(TelegramMediaWebpageLoadedContent)
|
case Loaded(TelegramMediaWebpageLoadedContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
|||||||
self.webpageId = MediaId(decoder.decodeBytesForKeyNoCopy("i")!)
|
self.webpageId = MediaId(decoder.decodeBytesForKeyNoCopy("i")!)
|
||||||
|
|
||||||
if decoder.decodeInt32ForKey("ct", orElse: 0) == 0 {
|
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 {
|
} else {
|
||||||
self.content = .Loaded(TelegramMediaWebpageLoadedContent(decoder: decoder))
|
self.content = .Loaded(TelegramMediaWebpageLoadedContent(decoder: decoder))
|
||||||
}
|
}
|
||||||
@ -225,9 +225,14 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
|||||||
encoder.encodeBytes(buffer, forKey: "i")
|
encoder.encodeBytes(buffer, forKey: "i")
|
||||||
|
|
||||||
switch self.content {
|
switch self.content {
|
||||||
case let .Pending(date):
|
case let .Pending(date, url):
|
||||||
encoder.encodeInt32(0, forKey: "ct")
|
encoder.encodeInt32(0, forKey: "ct")
|
||||||
encoder.encodeInt32(date, forKey: "pendingDate")
|
encoder.encodeInt32(date, forKey: "pendingDate")
|
||||||
|
if let url = url {
|
||||||
|
encoder.encodeString(url, forKey: "pendingUrl")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "pendingUrl")
|
||||||
|
}
|
||||||
case let .Loaded(loadedContent):
|
case let .Loaded(loadedContent):
|
||||||
encoder.encodeInt32(1, forKey: "ct")
|
encoder.encodeInt32(1, forKey: "ct")
|
||||||
loadedContent.encode(encoder)
|
loadedContent.encode(encoder)
|
||||||
@ -251,10 +256,14 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch lhs.content {
|
switch lhs.content {
|
||||||
case let .Pending(lhsDate):
|
case let .Pending(lhsDate, lhsUrl):
|
||||||
switch rhs.content {
|
switch rhs.content {
|
||||||
case let .Pending(rhsDate) where lhsDate == rhsDate:
|
case let .Pending(rhsDate, rhsUrl):
|
||||||
return true
|
if lhsDate == rhsDate, lhsUrl == rhsUrl {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false
|
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 {
|
switch webpage {
|
||||||
case .webPageNotModified:
|
case .webPageNotModified:
|
||||||
return nil
|
return nil
|
||||||
case let .webPagePending(id, date):
|
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):
|
case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage):
|
||||||
var embedSize: CGSize?
|
var embedSize: CGSize?
|
||||||
if let embedWidth = embedWidth, let embedHeight = embedHeight {
|
if let embedWidth = embedWidth, let embedHeight = embedHeight {
|
||||||
|
@ -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))))
|
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 = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
|
||||||
}
|
|
||||||
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)
|
modifier.setPeerChatState(currentPeer.id, state: updatedState)
|
||||||
updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
|
updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
|
||||||
return updated
|
return updated
|
||||||
|
@ -21,7 +21,7 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? =
|
|||||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
|
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
|
||||||
switch result {
|
switch result {
|
||||||
case let .messageMediaWebPage(webpage):
|
case let .messageMediaWebPage(webpage):
|
||||||
if let media = telegramMediaWebpageFromApiWebpage(webpage) {
|
if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) {
|
||||||
if case .Loaded = media.content {
|
if case .Loaded = media.content {
|
||||||
return .single(media)
|
return .single(media)
|
||||||
} else {
|
} else {
|
||||||
@ -45,9 +45,9 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg
|
|||||||
return .single(.webPageNotModified)
|
return .single(.webPageNotModified)
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage, NoError> in
|
|> mapToSignal { result -> Signal<TelegramMediaWebpage, NoError> 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
|
return postbox.modify { modifier -> TelegramMediaWebpage in
|
||||||
modifier.updateMedia(updatedWebpage.webpageId, update: updatedWebpage)
|
modifier.updateMedia(webpage.webpageId, update: updatedWebpage)
|
||||||
return updatedWebpage
|
return updatedWebpage
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user